diff --git a/ansible/playbooks/site.yml b/ansible/playbooks/site.yml
index 1ef12da..68139bc 100644
--- a/ansible/playbooks/site.yml
+++ b/ansible/playbooks/site.yml
@@ -7,6 +7,11 @@
compose_dir: "{{ hw8_root }}/compose"
compose_file: "{{ compose_dir }}/docker-compose.yml"
compose_env_file: "{{ compose_dir }}/.env"
+ jobs_profile_effective: "{{ jobs_profile | default('full') }}"
+ jjb_paths:
+ full: /workspace/hw8/config/jobs
+ devops: /workspace/hw8/config/jobs-devops
+ jjb_upload_path: "{{ jjb_paths.get(jobs_profile_effective, jjb_paths.full) }}"
tasks:
- name: Check Docker CLI availability
@@ -89,7 +94,7 @@
- name: Upload Jenkins jobs via JJB container
ansible.builtin.command:
- cmd: timeout 900 docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} run --rm jobs_uploader
+ cmd: timeout 900 docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} run --rm -e JJB_PATH={{ jjb_upload_path }} jobs_uploader
args:
chdir: "{{ compose_dir }}"
@@ -98,3 +103,5 @@
msg:
- "Jenkins UI: http://localhost:8088 (via nginx) or http://localhost:8081"
- "Registry: http://localhost:5005/v2/"
+ - "JJB profile: {{ jobs_profile_effective }}"
+ - "JJB path: {{ jjb_upload_path }}"
diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml
index 773cbec..2c1f5ca 100644
--- a/compose/docker-compose.yml
+++ b/compose/docker-compose.yml
@@ -33,7 +33,7 @@ services:
- jenkins_home:/var/jenkins_home
- ./jenkins/casc:/var/jenkins_home/casc_configs:ro
- /var/run/docker.sock:/var/run/docker.sock
- - ../..:/workspace/otus-autotests:ro
+ - ..:/workspace/hw8:ro
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/login >/dev/null"]
interval: 10s
@@ -67,9 +67,9 @@ services:
JENKINS_HOSTNAME: http://jenkins:8080
JENKINS_USERNAME: ${JENKINS_ADMIN_ID}
JENKINS_PASSWORD: ${JENKINS_ADMIN_PASSWORD}
- JJB_PATH: /workspace/otus-autotests/hw8/config/jobs
+ JJB_PATH: /workspace/hw8/config/jobs
volumes:
- - ../..:/workspace/otus-autotests:ro
+ - ..:/workspace/hw8:ro
networks:
- jenkins_net
@@ -87,9 +87,10 @@ services:
JENKINS_AGENT_WORKDIR: /home/jenkins/agent
JENKINS_WEB_SOCKET: "true"
JENKINS_LABELS: maven docker
+ HW8_ROOT: /workspace/hw8
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- - ../..:/workspace/otus-autotests:ro
+ - ..:/workspace/hw8:ro
networks:
- jenkins_net
@@ -107,9 +108,10 @@ services:
JENKINS_AGENT_WORKDIR: /home/jenkins/agent
JENKINS_WEB_SOCKET: "true"
JENKINS_LABELS: jjb docker
+ HW8_ROOT: /workspace/hw8
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- - ../..:/workspace/otus-autotests:ro
+ - ..:/workspace/hw8:ro
networks:
- jenkins_net
diff --git a/compose/images/slave-maven/Dockerfile b/compose/images/slave-maven/Dockerfile
index 518b99e..ffeafc6 100644
--- a/compose/images/slave-maven/Dockerfile
+++ b/compose/images/slave-maven/Dockerfile
@@ -8,6 +8,7 @@ RUN apt-get update \
ca-certificates \
curl \
docker.io \
+ git \
unzip \
&& rm -rf /var/lib/apt/lists/*
diff --git a/compose/images/test-selenium/Dockerfile b/compose/images/test-selenium/Dockerfile
index 98b0240..2befbd6 100644
--- a/compose/images/test-selenium/Dockerfile
+++ b/compose/images/test-selenium/Dockerfile
@@ -1,9 +1,55 @@
FROM maven:3.9.11-eclipse-temurin-21
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- chromium \
- chromium-driver \
- && rm -rf /var/lib/apt/lists/*
+RUN set -eux; \
+ apt-get update; \
+ apt-get install -y --no-install-recommends \
+ ca-certificates \
+ curl \
+ unzip \
+ fontconfig \
+ fonts-liberation \
+ libasound2t64 \
+ libatk-bridge2.0-0 \
+ libatk1.0-0 \
+ libc6 \
+ libcairo2 \
+ libcups2 \
+ libdbus-1-3 \
+ libexpat1 \
+ libfontconfig1 \
+ libgbm1 \
+ libgcc1 \
+ libglib2.0-0 \
+ libgtk-3-0 \
+ libnspr4 \
+ libnss3 \
+ libpango-1.0-0 \
+ libpangocairo-1.0-0 \
+ libstdc++6 \
+ libx11-6 \
+ libx11-xcb1 \
+ libxcb1 \
+ libxcomposite1 \
+ libxcursor1 \
+ libxdamage1 \
+ libxext6 \
+ libxfixes3 \
+ libxi6 \
+ libxrandr2 \
+ libxrender1 \
+ libxss1 \
+ libxtst6 \
+ xdg-utils; \
+ CHROME_VERSION="$(curl -fsSL https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_STABLE)"; \
+ curl -fsSL "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip" -o /tmp/chrome.zip; \
+ curl -fsSL "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chromedriver-linux64.zip" -o /tmp/chromedriver.zip; \
+ unzip -q /tmp/chrome.zip -d /opt; \
+ unzip -q /tmp/chromedriver.zip -d /opt; \
+ ln -sf /opt/chrome-linux64/chrome /usr/bin/google-chrome; \
+ ln -sf /opt/chrome-linux64/chrome /usr/bin/chromium; \
+ ln -sf /opt/chromedriver-linux64/chromedriver /usr/bin/chromedriver; \
+ chmod +x /opt/chrome-linux64/chrome /opt/chromedriver-linux64/chromedriver; \
+ rm -f /tmp/chrome.zip /tmp/chromedriver.zip; \
+ rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
diff --git a/compose/jenkins/casc/jenkins.yaml b/compose/jenkins/casc/jenkins.yaml
index 9acd3f2..bdaffdf 100644
--- a/compose/jenkins/casc/jenkins.yaml
+++ b/compose/jenkins/casc/jenkins.yaml
@@ -30,7 +30,7 @@ jenkins:
- key: JENKINS_URL_INTERNAL
value: "http://jenkins:8080"
- key: OTUS_WORKSPACE_ROOT
- value: "/workspace/otus-autotests"
+ value: "/workspace/hw8"
- key: MOBILE_DB_PASSWORD
value: "${MOBILE_DB_PASSWORD}"
diff --git a/config/compose/mobile-ci.compose.yml b/config/compose/mobile-ci.compose.yml
index 32884a8..a2673e5 100644
--- a/config/compose/mobile-ci.compose.yml
+++ b/config/compose/mobile-ci.compose.yml
@@ -24,7 +24,7 @@ services:
- EMULATOR_PARAMS=-no-window -no-audio -gpu swiftshader_indirect -no-snapshot -no-boot-anim
shm_size: 2gb
healthcheck:
- test: ["CMD-SHELL", "[ \"$(cat /home/androidusr/device_status 2>/dev/null)\" = \"READY\" ]"]
+ test: ["CMD-SHELL", "[ \"$(cat /home/androidusr/device_status 2>/dev/null)\" = \"READY\" ] && wget -qO- http://127.0.0.1:4723/status >/dev/null 2>&1"]
interval: 15s
timeout: 5s
retries: 40
@@ -45,7 +45,7 @@ services:
- EMULATOR_PARAMS=-no-window -no-audio -gpu swiftshader_indirect -no-snapshot -no-boot-anim
shm_size: 2gb
healthcheck:
- test: ["CMD-SHELL", "[ \"$(cat /home/androidusr/device_status 2>/dev/null)\" = \"READY\" ]"]
+ test: ["CMD-SHELL", "[ \"$(cat /home/androidusr/device_status 2>/dev/null)\" = \"READY\" ] && wget -qO- http://127.0.0.1:4723/status >/dev/null 2>&1"]
interval: 15s
timeout: 5s
retries: 40
diff --git a/config/jobs-devops/global.yaml b/config/jobs-devops/global.yaml
new file mode 100644
index 0000000..ff88d0c
--- /dev/null
+++ b/config/jobs-devops/global.yaml
@@ -0,0 +1,6 @@
+---
+- defaults:
+ name: global
+ project_folder: /workspace/hw8
+ test_image_tag: "1.0.0"
+ build_keep: 40
diff --git a/config/jobs-devops/templates/infra-health-check.yaml b/config/jobs-devops/templates/infra-health-check.yaml
new file mode 100644
index 0000000..c57d29a
--- /dev/null
+++ b/config/jobs-devops/templates/infra-health-check.yaml
@@ -0,0 +1,11 @@
+---
+- job:
+ name: infra-health-check
+ description: "Проверка инфраструктуры Jenkins/Registry/Agent образов."
+ project-type: pipeline
+ concurrent: false
+ sandbox: true
+ properties:
+ - build-discarder:
+ num-to-keep: 30
+ dsl: !include-raw-verbatim: ../../jobs/scripts/infra-health-check.groovy
diff --git a/config/jobs-devops/templates/jobs-uploader.yaml b/config/jobs-devops/templates/jobs-uploader.yaml
new file mode 100644
index 0000000..17a61cb
--- /dev/null
+++ b/config/jobs-devops/templates/jobs-uploader.yaml
@@ -0,0 +1,16 @@
+---
+- job:
+ name: jobs-uploader
+ description: "Обновляет Jenkins jobs из JJB YAML (config/jobs)."
+ project-type: pipeline
+ concurrent: false
+ sandbox: true
+ properties:
+ - build-discarder:
+ num-to-keep: 30
+ parameters:
+ - string:
+ name: JJB_PATH
+ default: /workspace/hw8/config/jobs
+ description: "Путь до JJB-конфигов"
+ dsl: !include-raw-verbatim: ../../jobs/scripts/jobs-uploader.groovy
diff --git a/config/jobs-devops/view.yaml b/config/jobs-devops/view.yaml
new file mode 100644
index 0000000..a98ba5e
--- /dev/null
+++ b/config/jobs-devops/view.yaml
@@ -0,0 +1,18 @@
+---
+- view:
+ name: DevOps
+ view-type: list
+ description: "Инфраструктурные job"
+ filter-executors: true
+ filter-queue: true
+ job-name:
+ - jobs-uploader
+ - infra-health-check
+ columns:
+ - status
+ - weather
+ - job
+ - last-success
+ - last-failure
+ - last-duration
+ - build-button
diff --git a/config/jobs/global.yaml b/config/jobs/global.yaml
index 8eb5f9e..ff88d0c 100644
--- a/config/jobs/global.yaml
+++ b/config/jobs/global.yaml
@@ -1,6 +1,6 @@
---
- defaults:
name: global
- project_folder: /workspace/otus-autotests
+ project_folder: /workspace/hw8
test_image_tag: "1.0.0"
build_keep: 40
diff --git a/config/jobs/scripts/infra-health-check.groovy b/config/jobs/scripts/infra-health-check.groovy
index 3f642e5..2868998 100644
--- a/config/jobs/scripts/infra-health-check.groovy
+++ b/config/jobs/scripts/infra-health-check.groovy
@@ -5,6 +5,18 @@ pipeline {
ansiColor('xterm')
}
stages {
+ stage('Prepare Metadata') {
+ steps {
+ script {
+ wrap([$class: 'BuildUser']) {
+ env.RUN_TRIGGER_USER = env.BUILD_USER_ID ?: env.BUILD_USER ?: 'system'
+ env.RUN_TRIGGER_NAME = env.BUILD_USER ?: env.BUILD_USER_ID ?: 'system'
+ }
+ currentBuild.displayName = "#${env.BUILD_NUMBER} infra"
+ currentBuild.description = "by=${env.RUN_TRIGGER_NAME}"
+ }
+ }
+ }
stage('Check Docker & Registry') {
steps {
sh '''
@@ -17,13 +29,12 @@ pipeline {
'''
}
}
- stage('Check Sources') {
+ stage('Check Job Configs') {
steps {
sh '''
set -eux
- test -f /workspace/otus-autotests/homework_4/pom.xml
- test -f /workspace/otus-autotests/hw7/pom.xml
- test -f /workspace/otus-autotests/hw8/config/jobs/global.yaml
+ HW8_ROOT_PATH="${HW8_ROOT:-/workspace/hw8}"
+ test -f "${HW8_ROOT_PATH}/config/jobs/global.yaml"
'''
}
}
diff --git a/config/jobs/scripts/jobs-uploader.groovy b/config/jobs/scripts/jobs-uploader.groovy
index 7db1cc2..88f07a1 100644
--- a/config/jobs/scripts/jobs-uploader.groovy
+++ b/config/jobs/scripts/jobs-uploader.groovy
@@ -5,6 +5,18 @@ pipeline {
ansiColor('xterm')
}
stages {
+ stage('Prepare Metadata') {
+ steps {
+ script {
+ wrap([$class: 'BuildUser']) {
+ env.RUN_TRIGGER_USER = env.BUILD_USER_ID ?: env.BUILD_USER ?: 'system'
+ env.RUN_TRIGGER_NAME = env.BUILD_USER ?: env.BUILD_USER_ID ?: 'system'
+ }
+ currentBuild.displayName = "#${env.BUILD_NUMBER} jjb"
+ currentBuild.description = "by=${env.RUN_TRIGGER_NAME}; jjbPath=${params.JJB_PATH}"
+ }
+ }
+ }
stage('Validate Input') {
steps {
sh '''
diff --git a/config/jobs/scripts/qa-api-citrus-tests.groovy b/config/jobs/scripts/qa-api-citrus-tests.groovy
index 08e7741..e02dcfd 100644
--- a/config/jobs/scripts/qa-api-citrus-tests.groovy
+++ b/config/jobs/scripts/qa-api-citrus-tests.groovy
@@ -5,19 +5,47 @@ pipeline {
ansiColor('xterm')
}
stages {
+ stage('Prepare Metadata') {
+ steps {
+ script {
+ wrap([$class: 'BuildUser']) {
+ env.RUN_TRIGGER_USER = env.BUILD_USER_ID ?: env.BUILD_USER ?: 'system'
+ env.RUN_TRIGGER_NAME = env.BUILD_USER ?: env.BUILD_USER_ID ?: 'system'
+ }
+ currentBuild.displayName = "#${env.BUILD_NUMBER} api"
+ currentBuild.description =
+ "by=${env.RUN_TRIGGER_NAME}; repo=${params.QA_REPO_URL}; ref=${params.QA_REPO_REF}"
+ }
+ }
+ }
stage('Run API Tests In Docker') {
steps {
sh '''
set -eux
+ rm -rf ./sources
+ git clone "${QA_REPO_URL}" ./sources
+ git -C ./sources checkout "${QA_REPO_REF}"
+ GIT_SHA="$(git -C ./sources rev-parse --short HEAD)"
+
rm -rf ./artifacts
mkdir -p ./artifacts
+ {
+ echo "job=${JOB_NAME}"
+ echo "build=${BUILD_NUMBER}"
+ echo "trigger_user=${RUN_TRIGGER_USER}"
+ echo "trigger_name=${RUN_TRIGGER_NAME}"
+ echo "qa_repo_url=${QA_REPO_URL}"
+ echo "qa_repo_ref=${QA_REPO_REF}"
+ echo "qa_repo_sha=${GIT_SHA}"
+ echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ } > ./artifacts/run-info.txt
CID="$(docker create localhost:5005/otus/test-api:1.0.0 bash -lc "set -e; cd /workspace; mvn -f citrus-tests/pom.xml test")"
cleanup_container() {
docker rm -f "${CID}" >/dev/null 2>&1 || true
}
trap cleanup_container EXIT INT TERM
- tar -C /workspace/otus-autotests/homework_4 -cf - . | docker cp - "${CID}:/workspace"
+ tar -C ./sources -cf - . | docker cp - "${CID}:/workspace"
set +e
docker start -a "${CID}"
TEST_RC=$?
@@ -32,7 +60,7 @@ pipeline {
post {
always {
junit allowEmptyResults: true, testResults: 'artifacts/citrus-target/surefire-reports/*.xml'
- archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/citrus-target/**'
+ archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/run-info.txt,artifacts/citrus-target/**'
}
}
}
diff --git a/config/jobs/scripts/qa-mobile-appium-tests.groovy b/config/jobs/scripts/qa-mobile-appium-tests.groovy
index efeb28d..7c24b91 100644
--- a/config/jobs/scripts/qa-mobile-appium-tests.groovy
+++ b/config/jobs/scripts/qa-mobile-appium-tests.groovy
@@ -5,13 +5,61 @@ pipeline {
ansiColor('xterm')
}
stages {
+ stage('Prepare Metadata') {
+ steps {
+ script {
+ wrap([$class: 'BuildUser']) {
+ env.RUN_TRIGGER_USER = env.BUILD_USER_ID ?: env.BUILD_USER ?: 'system'
+ env.RUN_TRIGGER_NAME = env.BUILD_USER ?: env.BUILD_USER_ID ?: 'system'
+ }
+ currentBuild.displayName = "#${env.BUILD_NUMBER} mobile"
+ currentBuild.description =
+ "by=${env.RUN_TRIGGER_NAME}; repo=${params.MOBILE_REPO_URL}; ref=${params.MOBILE_REPO_REF}; emulators=${params.MOBILE_MAX_EMULATORS}; junit=${params.JUNIT_PARALLELISM}; order=${params.TEST_CLASSES_ORDER}; rerun=${params.SUREFIRE_RERUN_FAILING}"
+ }
+ }
+ }
stage('Prepare Workspace') {
steps {
sh '''
set -eux
rm -rf ./project
- mkdir -p ./project
- cp -a /workspace/otus-autotests/hw7/. ./project/
+ git clone "${MOBILE_REPO_URL}" ./project
+ git -C ./project checkout "${MOBILE_REPO_REF}"
+ GIT_SHA="$(git -C ./project rev-parse --short HEAD)"
+ if ! grep -q "allure-junit5" ./project/pom.xml; then
+ awk -v ver="${ALLURE_ADAPTER_VERSION}" '
+ index($0, "") && !done {
+ print " "
+ print " io.qameta.allure"
+ print " allure-junit5"
+ print " " ver ""
+ print " test"
+ print " "
+ done=1
+ }
+ { print }
+ ' ./project/pom.xml > ./project/pom.xml.tmp
+ mv ./project/pom.xml.tmp ./project/pom.xml
+ fi
+ mkdir -p ./project/target/allure-results
+ {
+ echo "job=${JOB_NAME}"
+ echo "build=${BUILD_NUMBER}"
+ echo "trigger_user=${RUN_TRIGGER_USER}"
+ echo "trigger_name=${RUN_TRIGGER_NAME}"
+ echo "mobile_repo_url=${MOBILE_REPO_URL}"
+ echo "mobile_repo_ref=${MOBILE_REPO_REF}"
+ echo "mobile_repo_sha=${GIT_SHA}"
+ echo "app_url=${APP_URL}"
+ echo "mobile_max_emulators=${MOBILE_MAX_EMULATORS}"
+ echo "junit_parallelism=${JUNIT_PARALLELISM}"
+ echo "test_classes_order=${TEST_CLASSES_ORDER}"
+ echo "surefire_rerun_failing=${SUREFIRE_RERUN_FAILING}"
+ echo "db_url=${DB_URL}"
+ echo "db_user=${DB_USER}"
+ echo "reservation_owner=${RESERVATION_OWNER}"
+ echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ } > ./project/run-info.txt
'''
}
}
@@ -20,7 +68,8 @@ pipeline {
dir('project') {
sh '''
set -eux
- COMPOSE_FILE="/workspace/otus-autotests/hw8/config/compose/mobile-ci.compose.yml"
+ HW8_ROOT_PATH="${HW8_ROOT:-/workspace/hw8}"
+ COMPOSE_FILE="${HW8_ROOT_PATH}/config/compose/mobile-ci.compose.yml"
if docker compose version >/dev/null 2>&1; then
compose_cmd() { PROJECT_DIR="$PWD" docker compose -f "${COMPOSE_FILE}" "$@"; }
elif docker-compose version >/dev/null 2>&1; then
@@ -30,11 +79,32 @@ pipeline {
exit 1
fi
export COMPOSE_PROJECT_NAME=mobileci
- compose_cmd down -v --remove-orphans || true
- compose_cmd up -d wiremock android-emulator-1 android-emulator-2
+ MAX_EMULATORS="${MOBILE_MAX_EMULATORS:-1}"
+ if ! [ "${MAX_EMULATORS}" -eq "${MAX_EMULATORS}" ] 2>/dev/null; then
+ echo "Invalid MOBILE_MAX_EMULATORS='${MAX_EMULATORS}', expected integer"
+ exit 1
+ fi
+ if [ "${MAX_EMULATORS}" -lt 1 ] || [ "${MAX_EMULATORS}" -gt 2 ]; then
+ echo "Invalid MOBILE_MAX_EMULATORS='${MAX_EMULATORS}', expected 1 or 2"
+ exit 1
+ fi
+ SERVICES="wiremock android-emulator-1"
+ if [ "${MAX_EMULATORS}" -ge 2 ]; then
+ SERVICES="${SERVICES} android-emulator-2"
+ fi
+
+ compose_cmd down -v --remove-orphans || true
+ compose_cmd up -d ${SERVICES}
EMULATORS=""
+ SELECTED=0
for service in android-emulator-1 android-emulator-2; do
+ if [ "${service}" = "android-emulator-2" ] && [ "${MAX_EMULATORS}" -lt 2 ]; then
+ continue
+ fi
+ if [ "${SELECTED}" -ge "${MAX_EMULATORS}" ]; then
+ break
+ fi
cid="$(compose_cmd ps -q "$service")"
if [ -z "${cid}" ]; then
echo "Container for ${service} not found, skipping"
@@ -55,6 +125,7 @@ pipeline {
EMULATORS="${EMULATORS},"
fi
EMULATORS="${EMULATORS}${service}|http://${service}:4723|Android Emulator|${APP_URL}"
+ SELECTED=$((SELECTED + 1))
else
echo "Service ${service} is not healthy in time, excluding from test run"
docker logs "${cid}" || true
@@ -76,40 +147,258 @@ pipeline {
dir('project') {
sh '''
set -eux
- rm -rf ./target
+ rm -rf ./target ./target-run
MOBILE_EMULATORS_VALUE="$(cat ./.mobile_emulators.txt)"
+ EMULATOR_ENTRIES_COUNT="$(printf '%s' "${MOBILE_EMULATORS_VALUE}" | awk -F, '{print NF}')"
+ DOCKER_NETWORK_ARG="--network mobileci_default"
+ if [ "${EMULATOR_ENTRIES_COUNT}" -eq 1 ]; then
+ SINGLE_SERVICE="$(printf '%s' "${MOBILE_EMULATORS_VALUE}" | cut -d'|' -f1)"
+ if [ "${SINGLE_SERVICE}" = "android-emulator-1" ] || [ "${SINGLE_SERVICE}" = "android-emulator-2" ]; then
+ SINGLE_EMULATOR_CID="$(docker ps -q --filter "name=mobileci-${SINGLE_SERVICE}" | head -n1 || true)"
+ if [ -n "${SINGLE_EMULATOR_CID}" ]; then
+ DOCKER_NETWORK_ARG="--network container:${SINGLE_EMULATOR_CID}"
+ MOBILE_EMULATORS_VALUE="$(printf '%s' "${MOBILE_EMULATORS_VALUE}" | awk -F'|' 'BEGIN { OFS="|" } { $2="http://127.0.0.1:4723"; print $1, $2, $3, $4 }')"
+ fi
+ fi
+ fi
DB_PASSWORD_EFFECTIVE="${DB_PASSWORD:-${MOBILE_DB_PASSWORD:-}}"
if [ -z "${DB_PASSWORD_EFFECTIVE}" ]; then
echo "DB password is not set. Provide DB_PASSWORD job parameter or MOBILE_DB_PASSWORD env variable."
exit 1
fi
- CID="$(docker create \
- --network mobileci_default \
- -e DB_URL="${DB_URL:-}" \
- -e DB_USER="${DB_USER:-}" \
- -e DB_PASSWORD="${DB_PASSWORD_EFFECTIVE}" \
- -e APP_URL="${APP_URL:-}" \
+
+ # Validate Appium endpoints from the same Docker network as test container.
+ if ! docker run --rm \
+ ${DOCKER_NETWORK_ARG} \
-e MOBILE_EMULATORS="${MOBILE_EMULATORS_VALUE}" \
- -e WISHLISTS_USERNAME="${WISHLISTS_USERNAME:-}" \
- -e WISHLISTS_PASSWORD="${WISHLISTS_PASSWORD:-}" \
- -e GIFTS_USERNAME="${GIFTS_USERNAME:-}" \
- -e GIFTS_PASSWORD="${GIFTS_PASSWORD:-}" \
- -e RESERVATION_USERNAME="${RESERVATION_USERNAME:-}" \
- -e RESERVATION_PASSWORD="${RESERVATION_PASSWORD:-}" \
- -e RESERVATION_OWNER="${RESERVATION_OWNER:-}" \
localhost:5005/otus/test-mobile:1.0.0 \
- bash -lc "set -e; cd /workspace; timeout 1800s mvn -Dallure.results.directory=target/allure-results test")"
- cleanup_container() {
+ bash -lc '
+ set -euo pipefail
+ IFS="," read -ra TARGETS <<< "${MOBILE_EMULATORS}"
+ for target in "${TARGETS[@]}"; do
+ endpoint="$(echo "${target}" | cut -d"|" -f2)"
+ if [ -z "${endpoint}" ]; then
+ echo "Invalid MOBILE_EMULATORS entry: ${target}"
+ exit 1
+ fi
+ ready="false"
+ for i in $(seq 1 60); do
+ if curl -fsS "${endpoint}/status" >/dev/null 2>&1; then
+ ready="true"
+ break
+ fi
+ sleep 2
+ done
+ if [ "${ready}" != "true" ]; then
+ echo "Appium endpoint is not reachable from test container: ${endpoint}"
+ exit 1
+ fi
+ done
+ '; then
+ echo "Appium preflight failed. Dumping emulator logs."
+ for service in android-emulator-1 android-emulator-2; do
+ cid="$(docker ps -q --filter "name=mobileci-${service}" | head -n1 || true)"
+ if [ -n "${cid}" ]; then
+ echo "--- logs for ${service} (${cid}) ---"
+ docker logs --tail 200 "${cid}" || true
+ fi
+ done
+ exit 1
+ fi
+
+ run_single_class() {
+ class_name="$1"
+ attempt="$2"
+ class_key="$(echo "${class_name}" | sed 's/[^a-zA-Z0-9._-]/_/g')"
+ attempt_dir="./target-run/${class_key}/attempt-${attempt}"
+ mkdir -p "${attempt_dir}"
+
+ CID="$(docker create \
+ ${DOCKER_NETWORK_ARG} \
+ -e DB_URL="${DB_URL:-}" \
+ -e DB_USER="${DB_USER:-}" \
+ -e DB_PASSWORD="${DB_PASSWORD_EFFECTIVE}" \
+ -e APP_URL="${APP_URL:-}" \
+ -e MOBILE_EMULATORS="${MOBILE_EMULATORS_VALUE}" \
+ -e WISHLISTS_USERNAME="${WISHLISTS_USERNAME:-}" \
+ -e WISHLISTS_PASSWORD="${WISHLISTS_PASSWORD:-}" \
+ -e GIFTS_USERNAME="${GIFTS_USERNAME:-}" \
+ -e GIFTS_PASSWORD="${GIFTS_PASSWORD:-}" \
+ -e RESERVATION_USERNAME="${RESERVATION_USERNAME:-}" \
+ -e RESERVATION_PASSWORD="${RESERVATION_PASSWORD:-}" \
+ -e RESERVATION_OWNER="${RESERVATION_OWNER:-}" \
+ localhost:5005/otus/test-mobile:1.0.0 \
+ bash -lc "set -e; cd /workspace; timeout 1800s mvn -Dallure.version=${ALLURE_ADAPTER_VERSION} -Djunit.jupiter.execution.parallel.enabled=false -Djunit.jupiter.execution.parallel.config.fixed.parallelism=${JUNIT_PARALLELISM:-1} -Dtest=${class_name} -Dsurefire.rerunFailingTestsCount=${SUREFIRE_RERUN_FAILING:-0} -Dallure.results.directory=target/allure-results test")"
+
+ tar -C "$PWD" -cf - . | docker cp - "${CID}:/workspace"
+ set +e
+ docker start -a "${CID}"
+ class_rc=$?
+ set -e
+ docker cp "${CID}:/workspace/target" "${attempt_dir}/target" || true
docker rm -f "${CID}" >/dev/null 2>&1 || true
+ return "${class_rc}"
}
- trap cleanup_container EXIT INT TERM
- tar -C "$PWD" -cf - . | docker cp - "${CID}:/workspace"
- set +e
- docker start -a "${CID}"
- TEST_RC=$?
- docker cp "${CID}:/workspace/target" "./target" || true
- trap - EXIT INT TERM
- docker rm -f "${CID}" || true
+
+ collect_attempt_artifacts() {
+ src_dir="$1"
+ mkdir -p ./target/allure-results ./target/surefire-reports
+ if [ -d "${src_dir}/allure-results" ]; then
+ cp -a "${src_dir}/allure-results/." ./target/allure-results/ || true
+ fi
+ if [ -d "${src_dir}/surefire-reports" ]; then
+ cp -a "${src_dir}/surefire-reports/." ./target/surefire-reports/ || true
+ fi
+ }
+
+ if [ -n "${TEST_CLASSES_ORDER:-}" ]; then
+ printf '%s\n' "${TEST_CLASSES_ORDER}" | tr ',' '\n' > .test-classes.txt
+ else
+ cat > .test-classes.txt <<'EOF'
+ru.otus.mobile.tests.WishlistsTest
+ru.otus.mobile.tests.GiftsTest
+ru.otus.mobile.tests.ReservationTest
+EOF
+ fi
+
+ TEST_RC=0
+ MAX_CLASS_ATTEMPTS=2
+ while IFS= read -r raw_class; do
+ class_name="$(echo "${raw_class}" | xargs)"
+ if [ -z "${class_name}" ]; then
+ continue
+ fi
+ CLASS_OK=0
+ LAST_ATTEMPT_DIR=""
+ for attempt in $(seq 1 "${MAX_CLASS_ATTEMPTS}"); do
+ echo "Running ${class_name} (attempt ${attempt}/${MAX_CLASS_ATTEMPTS})"
+ if run_single_class "${class_name}" "${attempt}"; then
+ CLASS_OK=1
+ LAST_ATTEMPT_DIR="./target-run/$(echo "${class_name}" | sed 's/[^a-zA-Z0-9._-]/_/g')/attempt-${attempt}/target"
+ collect_attempt_artifacts "${LAST_ATTEMPT_DIR}"
+ break
+ fi
+ LAST_ATTEMPT_DIR="./target-run/$(echo "${class_name}" | sed 's/[^a-zA-Z0-9._-]/_/g')/attempt-${attempt}/target"
+ sleep 3
+ done
+
+ if [ "${CLASS_OK}" -ne 1 ]; then
+ TEST_RC=1
+ if [ -n "${LAST_ATTEMPT_DIR}" ] && [ -d "${LAST_ATTEMPT_DIR}" ]; then
+ collect_attempt_artifacts "${LAST_ATTEMPT_DIR}"
+ fi
+ fi
+ done < .test-classes.txt
+
+ mkdir -p ./target/mobile-debug
+ docker network inspect mobileci_default > ./target/mobile-debug/network.inspect.json 2>&1 || true
+ for service in android-emulator-1 android-emulator-2; do
+ emu_cid="$(docker ps -q --filter "name=mobileci-${service}" | head -n1 || true)"
+ if [ -n "${emu_cid}" ]; then
+ docker inspect "${emu_cid}" > "./target/mobile-debug/${service}.inspect.json" 2>&1 || true
+ docker logs "${emu_cid}" > "./target/mobile-debug/${service}.log" 2>&1 || true
+ fi
+ done
+ GIT_SHA="$(git -C "$PWD" rev-parse --short HEAD)"
+ mkdir -p ./target/allure-results
+
+ SCREENSHOT_FILES="$(find ./target ./target-run -type f \\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \\) 2>/dev/null || true)"
+ ATTACHMENT_COUNT=0
+ ATTACHMENTS_JSON=""
+ if [ -n "${SCREENSHOT_FILES}" ]; then
+ while IFS= read -r screenshot_file; do
+ if [ -z "${screenshot_file}" ] || [ ! -f "${screenshot_file}" ]; then
+ continue
+ fi
+ ATTACHMENT_COUNT=$((ATTACHMENT_COUNT + 1))
+ ext="$(echo "${screenshot_file}" | awk -F. '{print tolower($NF)}')"
+ if [ "${ext}" = "jpg" ] || [ "${ext}" = "jpeg" ]; then
+ mime="image/jpeg"
+ else
+ ext="png"
+ mime="image/png"
+ fi
+ source_name="external-screenshot-${ATTACHMENT_COUNT}.${ext}"
+ cp "${screenshot_file}" "./target/allure-results/${source_name}" || continue
+ safe_name="$(basename "${screenshot_file}" | sed 's/"/\\"/g')"
+ if [ -n "${ATTACHMENTS_JSON}" ]; then
+ ATTACHMENTS_JSON="${ATTACHMENTS_JSON},"
+ fi
+ ATTACHMENTS_JSON="${ATTACHMENTS_JSON}{\"name\":\"${safe_name}\",\"type\":\"${mime}\",\"source\":\"${source_name}\"}"
+ done < "./target/allure-results/${EXTRA_UUID}-result.json" < ./target/allure-results/environment.properties
+ cat > ./target/allure-results/executor.json < ./target/allure-results/categories.json <<'EOF'
+[
+ {
+ "name": "Infrastructure issues",
+ "matchedStatuses": ["broken"],
+ "messageRegex": ".*(Connection refused|No route to host|timed out|timeout|No healthy emulators available).*"
+ },
+ {
+ "name": "Mobile UI locator/assertion issues",
+ "matchedStatuses": ["failed", "broken"],
+ "messageRegex": ".*(Element not found|NoSuchElement|AssertionError).*"
+ }
+]
+EOF
exit "${TEST_RC}"
'''
}
@@ -121,7 +410,8 @@ pipeline {
dir('project') {
sh '''
set +e
- COMPOSE_FILE="/workspace/otus-autotests/hw8/config/compose/mobile-ci.compose.yml"
+ HW8_ROOT_PATH="${HW8_ROOT:-/workspace/hw8}"
+ COMPOSE_FILE="${HW8_ROOT_PATH}/config/compose/mobile-ci.compose.yml"
if docker compose version >/dev/null 2>&1; then
compose_cmd() { PROJECT_DIR="$PWD" docker compose -f "${COMPOSE_FILE}" "$@"; }
elif docker-compose version >/dev/null 2>&1; then
@@ -142,7 +432,7 @@ pipeline {
}
}
junit allowEmptyResults: true, testResults: 'project/target/surefire-reports/*.xml'
- archiveArtifacts allowEmptyArchive: true, artifacts: 'project/target/**'
+ archiveArtifacts allowEmptyArchive: true, artifacts: 'project/run-info.txt,project/target/**'
}
}
}
diff --git a/config/jobs/scripts/qa-runner.groovy b/config/jobs/scripts/qa-runner.groovy
index 2558f17..2273307 100644
--- a/config/jobs/scripts/qa-runner.groovy
+++ b/config/jobs/scripts/qa-runner.groovy
@@ -5,29 +5,48 @@ pipeline {
ansiColor('xterm')
}
stages {
+ stage('Prepare Metadata') {
+ steps {
+ script {
+ wrap([$class: 'BuildUser']) {
+ env.RUN_TRIGGER_USER = env.BUILD_USER_ID ?: env.BUILD_USER ?: 'system'
+ env.RUN_TRIGGER_NAME = env.BUILD_USER ?: env.BUILD_USER_ID ?: 'system'
+ }
+ currentBuild.displayName = "#${env.BUILD_NUMBER} runner"
+ currentBuild.description = "by=${env.RUN_TRIGGER_NAME}; qaRef=${params.QA_REPO_REF}; mobileRef=${params.MOBILE_REPO_REF}"
+ }
+ }
+ }
stage('Run Jobs In Parallel') {
steps {
script {
def fanout = [:]
fanout['selenium'] = {
- build job: 'qa-selenium-tests',
+ def run = build job: 'qa-selenium-tests',
wait: true,
- propagate: true,
+ propagate: false,
parameters: [
+ string(name: 'QA_REPO_URL', value: params.QA_REPO_URL),
+ string(name: 'QA_REPO_REF', value: params.QA_REPO_REF),
string(name: 'BROWSER', value: params.BROWSER),
string(name: 'BASE_URL', value: params.BASE_URL),
string(name: 'EXECUTION_MODE', value: params.EXECUTION_MODE),
string(name: 'SELENOID_URL', value: params.SELENOID_URL),
string(name: 'HEADLESS', value: params.HEADLESS)
]
+ writeFile file: 'downstream-selenium.txt', text: "${run.number}|${run.result}\n"
}
fanout['mobile'] = {
- build job: 'qa-mobile-appium-tests',
+ def run = build job: 'qa-mobile-appium-tests',
wait: true,
- propagate: true,
+ propagate: false,
parameters: [
+ string(name: 'MOBILE_REPO_URL', value: params.MOBILE_REPO_URL),
+ string(name: 'MOBILE_REPO_REF', value: params.MOBILE_REPO_REF),
+ string(name: 'MOBILE_MAX_EMULATORS', value: params.MOBILE_MAX_EMULATORS),
+ string(name: 'JUNIT_PARALLELISM', value: params.JUNIT_PARALLELISM),
string(name: 'APP_URL', value: params.APP_URL),
string(name: 'DB_URL', value: params.DB_URL),
string(name: 'DB_USER', value: params.DB_USER),
@@ -40,13 +59,64 @@ pipeline {
string(name: 'RESERVATION_PASSWORD', value: params.RESERVATION_PASSWORD),
string(name: 'RESERVATION_OWNER', value: params.RESERVATION_OWNER)
]
+ writeFile file: 'downstream-mobile.txt', text: "${run.number}|${run.result}\n"
}
fanout['api'] = {
- build job: 'qa-api-citrus-tests', wait: true, propagate: true
+ def run = build job: 'qa-api-citrus-tests',
+ wait: true,
+ propagate: false,
+ parameters: [
+ string(name: 'QA_REPO_URL', value: params.QA_REPO_URL),
+ string(name: 'QA_REPO_REF', value: params.QA_REPO_REF)
+ ]
+ writeFile file: 'downstream-api.txt', text: "${run.number}|${run.result}\n"
}
parallel fanout
+
+ def parseRun = { String fileName ->
+ if (!fileExists(fileName)) {
+ return [number: 'n/a', result: 'NOT_BUILT']
+ }
+ def parts = readFile(fileName).trim().tokenize('|')
+ return [
+ number: parts ? parts[0] : 'n/a',
+ result: parts.size() > 1 ? parts[1] : 'UNKNOWN'
+ ]
+ }
+
+ def seleniumRun = parseRun('downstream-selenium.txt')
+ def mobileRun = parseRun('downstream-mobile.txt')
+ def apiRun = parseRun('downstream-api.txt')
+ def lines = []
+ lines << "job=${env.JOB_NAME}"
+ lines << "build=${env.BUILD_NUMBER}"
+ lines << "trigger_user=${env.RUN_TRIGGER_USER}"
+ lines << "trigger_name=${env.RUN_TRIGGER_NAME}"
+ lines << "qa_repo_url=${params.QA_REPO_URL}"
+ lines << "qa_repo_ref=${params.QA_REPO_REF}"
+ lines << "mobile_repo_url=${params.MOBILE_REPO_URL}"
+ lines << "mobile_repo_ref=${params.MOBILE_REPO_REF}"
+ lines << "selenium_build=${seleniumRun.number}"
+ lines << "selenium_result=${seleniumRun.result}"
+ lines << "mobile_build=${mobileRun.number}"
+ lines << "mobile_result=${mobileRun.result}"
+ lines << "api_build=${apiRun.number}"
+ lines << "api_result=${apiRun.result}"
+ lines << "selenium_link=/job/qa-selenium-tests/${seleniumRun.number}/"
+ lines << "mobile_link=/job/qa-mobile-appium-tests/${mobileRun.number}/"
+ lines << "api_link=/job/qa-api-citrus-tests/${apiRun.number}/"
+ lines << "selenium_link_abs=${env.JENKINS_URL}job/qa-selenium-tests/${seleniumRun.number}/"
+ lines << "mobile_link_abs=${env.JENKINS_URL}job/qa-mobile-appium-tests/${mobileRun.number}/"
+ lines << "api_link_abs=${env.JENKINS_URL}job/qa-api-citrus-tests/${apiRun.number}/"
+ writeFile file: 'runner-summary.txt', text: lines.join('\n') + '\n'
+ archiveArtifacts allowEmptyArchive: false, artifacts: 'runner-summary.txt'
+
+ def failed = [seleniumRun, mobileRun, apiRun].any { it.result != 'SUCCESS' }
+ if (failed) {
+ error("One or more downstream QA jobs failed. See runner-summary.txt for build links and statuses.")
+ }
}
}
}
diff --git a/config/jobs/scripts/qa-selenium-tests.groovy b/config/jobs/scripts/qa-selenium-tests.groovy
index c859b71..13619fc 100644
--- a/config/jobs/scripts/qa-selenium-tests.groovy
+++ b/config/jobs/scripts/qa-selenium-tests.groovy
@@ -5,12 +5,60 @@ pipeline {
ansiColor('xterm')
}
stages {
+ stage('Prepare Metadata') {
+ steps {
+ script {
+ wrap([$class: 'BuildUser']) {
+ env.RUN_TRIGGER_USER = env.BUILD_USER_ID ?: env.BUILD_USER ?: 'system'
+ env.RUN_TRIGGER_NAME = env.BUILD_USER ?: env.BUILD_USER_ID ?: 'system'
+ }
+ currentBuild.displayName = "#${env.BUILD_NUMBER} ${params.BROWSER}/${params.HEADLESS}"
+ currentBuild.description =
+ "by=${env.RUN_TRIGGER_NAME}; repo=${params.QA_REPO_URL}; ref=${params.QA_REPO_REF}; mode=${params.EXECUTION_MODE}"
+ }
+ }
+ }
stage('Run Selenium Tests In Docker') {
steps {
sh '''
set -eux
+ rm -rf ./sources
+ git clone "${QA_REPO_URL}" ./sources
+ git -C ./sources checkout "${QA_REPO_REF}"
+ GIT_SHA="$(git -C ./sources rev-parse --short HEAD)"
+ if ! grep -q "allure-junit5" ./sources/pom.xml; then
+ awk -v ver="${ALLURE_ADAPTER_VERSION}" '
+ index($0, "") && !done {
+ print " "
+ print " io.qameta.allure"
+ print " allure-junit5"
+ print " " ver ""
+ print " test"
+ print " "
+ done=1
+ }
+ { print }
+ ' ./sources/pom.xml > ./sources/pom.xml.tmp
+ mv ./sources/pom.xml.tmp ./sources/pom.xml
+ fi
+
rm -rf ./artifacts
mkdir -p ./artifacts
+ {
+ echo "job=${JOB_NAME}"
+ echo "build=${BUILD_NUMBER}"
+ echo "trigger_user=${RUN_TRIGGER_USER}"
+ echo "trigger_name=${RUN_TRIGGER_NAME}"
+ echo "qa_repo_url=${QA_REPO_URL}"
+ echo "qa_repo_ref=${QA_REPO_REF}"
+ echo "qa_repo_sha=${GIT_SHA}"
+ echo "browser=${BROWSER}"
+ echo "headless=${HEADLESS}"
+ echo "execution_mode=${EXECUTION_MODE}"
+ echo "selenoid_url=${SELENOID_URL}"
+ echo "base_url=${BASE_URL}"
+ echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ } > ./artifacts/run-info.txt
EXTRA_ARGS=""
if [ "${BROWSER}" = "chrome" ]; then
@@ -20,16 +68,55 @@ pipeline {
CID="$(docker create \
--add-host=host.docker.internal:host-gateway \
localhost:5005/otus/test-selenium:1.0.0 \
- bash -lc "set -e; cd /workspace; mvn -Dexecution.mode=${EXECUTION_MODE} -Dbrowser=${BROWSER} -Dbrowser.version= -Dselenoid.url=${SELENOID_URL} -Dbase.url=${BASE_URL} -Dselenide.headless=${HEADLESS} -Dallure.results.directory=target/allure-results ${EXTRA_ARGS} test")"
+ bash -lc "set -e; cd /workspace; mvn -Dallure.version=${ALLURE_ADAPTER_VERSION} -Dexecution.mode=${EXECUTION_MODE} -Dbrowser=${BROWSER} -Dbrowser.version= -Dselenoid.url=${SELENOID_URL} -Dbase.url=${BASE_URL} -Dselenide.headless=${HEADLESS} -Dallure.results.directory=target/allure-results ${EXTRA_ARGS} test")"
cleanup_container() {
docker rm -f "${CID}" >/dev/null 2>&1 || true
}
trap cleanup_container EXIT INT TERM
- tar -C /workspace/otus-autotests/homework_4 -cf - . | docker cp - "${CID}:/workspace"
+ tar -C ./sources -cf - . | docker cp - "${CID}:/workspace"
set +e
docker start -a "${CID}"
TEST_RC=$?
- docker cp "${CID}:/workspace/target" "./artifacts/target" || true
+ docker cp "${CID}:/workspace/target" "./artifacts" || true
+ mkdir -p ./artifacts/target/allure-results
+ {
+ echo "job=${JOB_NAME}"
+ echo "build=${BUILD_NUMBER}"
+ echo "trigger.user=${RUN_TRIGGER_USER}"
+ echo "trigger.name=${RUN_TRIGGER_NAME}"
+ echo "repo.url=${QA_REPO_URL}"
+ echo "repo.ref=${QA_REPO_REF}"
+ echo "repo.sha=${GIT_SHA}"
+ echo "browser=${BROWSER}"
+ echo "headless=${HEADLESS}"
+ echo "execution.mode=${EXECUTION_MODE}"
+ echo "base.url=${BASE_URL}"
+ } > ./artifacts/target/allure-results/environment.properties
+ cat > ./artifacts/target/allure-results/executor.json < ./artifacts/target/allure-results/categories.json <<'EOF'
+[
+ {
+ "name": "Infrastructure issues",
+ "matchedStatuses": ["broken"],
+ "messageRegex": ".*(Connection refused|No route to host|timed out|timeout).*"
+ },
+ {
+ "name": "UI locator/assertion issues",
+ "matchedStatuses": ["failed", "broken"],
+ "messageRegex": ".*(Element not found|NoSuchElement|AssertionError).*"
+ }
+]
+EOF
trap - EXIT INT TERM
docker rm -f "${CID}" || true
exit "${TEST_RC}"
@@ -47,7 +134,7 @@ pipeline {
}
}
junit allowEmptyResults: true, testResults: 'artifacts/target/surefire-reports/*.xml'
- archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/target/**'
+ archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/run-info.txt,artifacts/target/**'
}
}
}
diff --git a/config/jobs/templates/jobs-uploader.yaml b/config/jobs/templates/jobs-uploader.yaml
index 197e680..6c94bbf 100644
--- a/config/jobs/templates/jobs-uploader.yaml
+++ b/config/jobs/templates/jobs-uploader.yaml
@@ -11,6 +11,6 @@
parameters:
- string:
name: JJB_PATH
- default: /workspace/otus-autotests/hw8/config/jobs
+ default: /workspace/hw8/config/jobs
description: "Путь до JJB-конфигов"
dsl: !include-raw-verbatim: ../scripts/jobs-uploader.groovy
diff --git a/config/jobs/templates/qa-api-citrus-tests.yaml b/config/jobs/templates/qa-api-citrus-tests.yaml
index 0e04bf4..bfede70 100644
--- a/config/jobs/templates/qa-api-citrus-tests.yaml
+++ b/config/jobs/templates/qa-api-citrus-tests.yaml
@@ -8,4 +8,13 @@
properties:
- build-discarder:
num-to-keep: 50
+ parameters:
+ - string:
+ name: QA_REPO_URL
+ default: https://git.kovbasa.ru/otus-autotests/homework_4.git
+ description: "Git URL репозитория с Selenium/API тестами"
+ - string:
+ name: QA_REPO_REF
+ default: master
+ description: "Git branch/tag/commit для checkout"
dsl: !include-raw-verbatim: ../scripts/qa-api-citrus-tests.groovy
diff --git a/config/jobs/templates/qa-mobile-appium-tests.yaml b/config/jobs/templates/qa-mobile-appium-tests.yaml
index c2eacb4..b5666c1 100644
--- a/config/jobs/templates/qa-mobile-appium-tests.yaml
+++ b/config/jobs/templates/qa-mobile-appium-tests.yaml
@@ -9,6 +9,38 @@
- build-discarder:
num-to-keep: 30
parameters:
+ - string:
+ name: MOBILE_REPO_URL
+ default: https://git.kovbasa.ru/otus-autotests/homework_7.git
+ description: "Git URL репозитория с Appium тестами"
+ - string:
+ name: MOBILE_REPO_REF
+ default: master
+ description: "Git branch/tag/commit для checkout"
+ - string:
+ name: ALLURE_ADAPTER_VERSION
+ default: 2.29.1
+ description: "Версия allure-junit5 адаптера"
+ - choice:
+ name: MOBILE_MAX_EMULATORS
+ choices:
+ - "1"
+ - "2"
+ description: "Сколько эмуляторов использовать в прогоне (1=стабильнее, 2=быстрее)"
+ - choice:
+ name: JUNIT_PARALLELISM
+ choices:
+ - "1"
+ - "2"
+ description: "Параллельность JUnit классов в мобильных тестах"
+ - string:
+ name: TEST_CLASSES_ORDER
+ default: "ru.otus.mobile.tests.WishlistsTest,ru.otus.mobile.tests.GiftsTest,ru.otus.mobile.tests.ReservationTest"
+ description: "Порядок классов для поочередного запуска (-Dtest)"
+ - string:
+ name: SUREFIRE_RERUN_FAILING
+ default: "0"
+ description: "Количество surefire rerun внутри одного запуска класса (обычно 0, т.к. ретрай делается на уровне pipeline)"
- string:
name: APP_URL
default: http://wiremock:8080/wishlist.apk
diff --git a/config/jobs/templates/qa-runner.yaml b/config/jobs/templates/qa-runner.yaml
index 1ae725d..3886041 100644
--- a/config/jobs/templates/qa-runner.yaml
+++ b/config/jobs/templates/qa-runner.yaml
@@ -9,6 +9,28 @@
- build-discarder:
num-to-keep: 50
parameters:
+ - string:
+ name: QA_REPO_URL
+ default: https://git.kovbasa.ru/otus-autotests/homework_4.git
+ - string:
+ name: QA_REPO_REF
+ default: master
+ - string:
+ name: MOBILE_REPO_URL
+ default: https://git.kovbasa.ru/otus-autotests/homework_7.git
+ - string:
+ name: MOBILE_REPO_REF
+ default: master
+ - choice:
+ name: MOBILE_MAX_EMULATORS
+ choices:
+ - "1"
+ - "2"
+ - choice:
+ name: JUNIT_PARALLELISM
+ choices:
+ - "1"
+ - "2"
- choice:
name: BROWSER
choices:
@@ -19,7 +41,7 @@
default: https://otus.ru
- string:
name: EXECUTION_MODE
- default: selenoid
+ default: local
- string:
name: SELENOID_URL
default: http://host.docker.internal:4444/wd/hub
diff --git a/config/jobs/templates/qa-selenium-tests.yaml b/config/jobs/templates/qa-selenium-tests.yaml
index 336064f..2dbcbd4 100644
--- a/config/jobs/templates/qa-selenium-tests.yaml
+++ b/config/jobs/templates/qa-selenium-tests.yaml
@@ -9,6 +9,18 @@
- build-discarder:
num-to-keep: 50
parameters:
+ - string:
+ name: QA_REPO_URL
+ default: https://git.kovbasa.ru/otus-autotests/homework_4.git
+ description: "Git URL репозитория с Selenium/API тестами"
+ - string:
+ name: QA_REPO_REF
+ default: master
+ description: "Git branch/tag/commit для checkout"
+ - string:
+ name: ALLURE_ADAPTER_VERSION
+ default: 2.29.1
+ description: "Версия allure-junit5 адаптера"
- choice:
name: BROWSER
choices:
@@ -21,7 +33,7 @@
description: "Базовый URL тестируемого сайта"
- string:
name: EXECUTION_MODE
- default: selenoid
+ default: local
description: "Режим запуска (local|selenoid)"
- string:
name: SELENOID_URL
diff --git a/config/jobs/view.yaml b/config/jobs/view.yaml
index fe4d6ab..10ec713 100644
--- a/config/jobs/view.yaml
+++ b/config/jobs/view.yaml
@@ -18,13 +18,29 @@
- build-button
- view:
- name: QA
+ name: QA-Runner
view-type: list
- description: "Тестовые job и раннер"
+ description: "Точка входа для запуска всех тестов"
filter-executors: true
filter-queue: true
job-name:
- qa-runner
+ columns:
+ - status
+ - weather
+ - job
+ - last-success
+ - last-failure
+ - last-duration
+ - build-button
+
+- view:
+ name: QA-Tests
+ view-type: list
+ description: "Тестовые job"
+ filter-executors: true
+ filter-queue: true
+ job-name:
- qa-selenium-tests
- qa-api-citrus-tests
- qa-mobile-appium-tests