feat: finalize projectwork CI jobs, docs and test integration
This commit is contained in:
@@ -6,3 +6,7 @@ build/
|
|||||||
jenkins_home/
|
jenkins_home/
|
||||||
compose/.env
|
compose/.env
|
||||||
lib/
|
lib/
|
||||||
|
compose/selenoid/video/*
|
||||||
|
!compose/selenoid/video/.gitkeep
|
||||||
|
compose/selenoid/logs/*
|
||||||
|
!compose/selenoid/logs/.gitkeep
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
# OTUS HW8: Jenkins + Ansible + JJB + Docker
|
# OTUS ProjectWork: Jenkins + Ansible + JJB + Docker
|
||||||
|
|
||||||
Полностью автоматизированная раскатка ДЗ8:
|
Полностью автоматизированная раскатка проектной работы:
|
||||||
- Jenkins controller в Docker;
|
- Jenkins controller в Docker;
|
||||||
- reverse proxy Nginx;
|
- reverse proxy Nginx;
|
||||||
- локальный Docker Registry;
|
- локальный Docker Registry;
|
||||||
- Jenkins agents (docker slaves);
|
- Jenkins agents (docker slaves);
|
||||||
- Jenkins Job Builder (JJB), без ручного создания job;
|
- Jenkins Job Builder (JJB), без ручного создания job;
|
||||||
- Ansible для полного подъема/сноса стенда;
|
- Ansible для полного подъема/сноса стенда;
|
||||||
- 6 job (runner + uploader + 3 тестовые + infra check);
|
- Selenoid + Selenoid UI;
|
||||||
- Allure отчеты для UI и mobile.
|
- расширенный набор job (web/api/mobile + runner + uploader + infra check);
|
||||||
|
- Allure отчеты для всех контуров.
|
||||||
|
|
||||||
Тестовые проекты всегда берутся из Git в runtime:
|
Тестовые проекты берутся из Git в runtime:
|
||||||
- `homework_4` (Selenium + API Citrus);
|
- `homework_1` (`main`, `homework_2`) - WEB BDD/Cucumber;
|
||||||
- `homework_7` (Appium, APK скачивается автоматически через `APP_URL`).
|
- `hw3` - API REST;
|
||||||
|
- `homework_4` - Selenium + API Citrus;
|
||||||
|
- `hw3` (`homework_5`) - HW5 как ветка в репозитории `hw3` (job `qa-maven-extra-tests`);
|
||||||
|
- `homework_6` - Playwright;
|
||||||
|
- `homework_7` - Appium, APK скачивается автоматически через `APP_URL`;
|
||||||
|
|
||||||
## Что поднимается
|
## Что поднимается
|
||||||
|
|
||||||
@@ -20,20 +25,27 @@
|
|||||||
- Jenkins: `http://localhost:8081`
|
- Jenkins: `http://localhost:8081`
|
||||||
- Jenkins через Nginx: `http://localhost:8088`
|
- Jenkins через Nginx: `http://localhost:8088`
|
||||||
- Registry: `http://localhost:5005/v2/`
|
- Registry: `http://localhost:5005/v2/`
|
||||||
|
- Selenoid: `http://localhost:4444/wd/hub`
|
||||||
|
- Selenoid UI: `http://localhost:8089`
|
||||||
- Agent `maven` (docker label: `maven docker`)
|
- Agent `maven` (docker label: `maven docker`)
|
||||||
- Agent `jjb` (docker label: `jjb docker`)
|
- Agent `jjb` (docker label: `jjb docker`)
|
||||||
- one-shot `jobs_uploader` container
|
- one-shot `jobs_uploader` container
|
||||||
|
|
||||||
Контроллер не исполняет сборки (`numExecutors: 0`, mode `EXCLUSIVE`), все job идут только на агентах.
|
Контроллер не исполняет сборки (`numExecutors: 0`, mode `EXCLUSIVE`), все job идут только на агентах.
|
||||||
|
|
||||||
## 6 job
|
## Набор job
|
||||||
|
|
||||||
1. `jobs-uploader` - накатывает/обновляет job через JJB.
|
1. `jobs-uploader` - накатывает/обновляет job через JJB.
|
||||||
2. `infra-health-check` - проверка Jenkins/Registry/образов/агентов.
|
2. `infra-health-check` - проверка Jenkins/Registry/образов/агентов.
|
||||||
3. `qa-selenium-tests` - Selenium/Selenide с выбором браузера.
|
3. `qa-runner` - оркестратор параллельного запуска всех тестовых контуров (cron: `15 1 * * *`).
|
||||||
4. `qa-api-citrus-tests` - API тесты.
|
4. `qa-selenium-tests`
|
||||||
5. `qa-mobile-appium-tests` - Appium тесты, APK через `APP_URL`.
|
5. `qa-web-bdd-tests`
|
||||||
6. `qa-runner` - параллельный запуск `selenium + api + mobile`.
|
6. `qa-api-citrus-tests`
|
||||||
|
7. `qa-api-contract-tests`
|
||||||
|
8. `qa-api-rest-tests`
|
||||||
|
9. `qa-playwright-tests`
|
||||||
|
10. `qa-maven-extra-tests`
|
||||||
|
11. `qa-mobile-appium-tests`
|
||||||
|
|
||||||
Вьюхи в Jenkins:
|
Вьюхи в Jenkins:
|
||||||
- `DevOps`
|
- `DevOps`
|
||||||
@@ -59,7 +71,7 @@
|
|||||||
Подготовка:
|
Подготовка:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /path/to/hw8
|
cd /path/to/projectwork
|
||||||
cp compose/.env.example compose/.env
|
cp compose/.env.example compose/.env
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -77,10 +89,10 @@ ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml -e jo
|
|||||||
|
|
||||||
`wsl`:
|
`wsl`:
|
||||||
```powershell
|
```powershell
|
||||||
wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/hw8 && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml -e jobs_profile=devops"
|
wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/projectwork && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml -e jobs_profile=devops"
|
||||||
```
|
```
|
||||||
|
|
||||||
После этого запускаете `jobs-uploader` (дефолт `JJB_PATH=/workspace/hw8/config/jobs`) и получаете QA job.
|
После этого запускаете `jobs-uploader` (дефолт `JJB_PATH=/workspace/projectwork/config/jobs`) и получаете QA job.
|
||||||
|
|
||||||
### Вариант 2: сразу все job
|
### Вариант 2: сразу все job
|
||||||
|
|
||||||
@@ -91,7 +103,7 @@ ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml
|
|||||||
|
|
||||||
`wsl`:
|
`wsl`:
|
||||||
```powershell
|
```powershell
|
||||||
wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/hw8 && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml"
|
wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/projectwork && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml"
|
||||||
```
|
```
|
||||||
|
|
||||||
Примечание: можно запускать и без `-e jobs_profile=full` — по умолчанию в `site.yml` используется профиль `full` (раскатываются все job).
|
Примечание: можно запускать и без `-e jobs_profile=full` — по умолчанию в `site.yml` используется профиль `full` (раскатываются все job).
|
||||||
@@ -100,56 +112,67 @@ wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/hw8 && ansible-p
|
|||||||
|
|
||||||
Рекомендуемый вход: `QA-Runner` -> `qa-runner` -> `Build with Parameters`.
|
Рекомендуемый вход: `QA-Runner` -> `qa-runner` -> `Build with Parameters`.
|
||||||
|
|
||||||
`qa-runner` запускает в параллели:
|
`qa-runner` запускает в параллели все контуры и всегда публикует `runner-summary.txt` с:
|
||||||
- `qa-selenium-tests`
|
|
||||||
- `qa-api-citrus-tests`
|
|
||||||
- `qa-mobile-appium-tests`
|
|
||||||
|
|
||||||
И всегда публикует `runner-summary.txt` с:
|
|
||||||
- номерами дочерних сборок;
|
- номерами дочерних сборок;
|
||||||
- статусами;
|
- статусами;
|
||||||
- ссылками на каждую дочернюю job.
|
- ссылками на каждую дочернюю job.
|
||||||
|
|
||||||
|
Для BDD по умолчанию запускаются обе ветки из `homework_1`: `main` и `homework_2` (`BDD_REFS=main,homework_2`).
|
||||||
|
|
||||||
## Отчетность
|
## Отчетность
|
||||||
|
|
||||||
`qa-selenium-tests` и `qa-mobile-appium-tests` публикуют:
|
Все тестовые job публикуют:
|
||||||
- Allure report (`/allure`);
|
- Allure report (`/allure`);
|
||||||
- JUnit результаты;
|
- JUnit результаты;
|
||||||
- `run-info.txt`;
|
- `run-info.txt`;
|
||||||
- `target/**` артефакты (включая скриншоты/логи тестового проекта, если они туда пишутся).
|
- `target/**` артефакты (или аналогичные артефакты проекта).
|
||||||
- для mobile дополнительно сохраняются `project/target/mobile-debug/*` (`docker logs` и `docker inspect` эмуляторов/сети).
|
|
||||||
- для mobile параметр `SUREFIRE_RERUN_FAILING` (по умолчанию `1`) снижает flaky-падения Appium-сессий без правок кода HW7.
|
|
||||||
|
|
||||||
Дополнительно в Allure:
|
Дополнительно в Allure:
|
||||||
- `environment.properties`
|
- `environment.properties`
|
||||||
- `executor.json`
|
- `executor.json`
|
||||||
- `categories.json`
|
- `categories.json` (там, где применимо)
|
||||||
|
|
||||||
Также в `currentBuild.description` пишется, кто запустил job и ключевые параметры.
|
Для `qa-selenium-tests` и `qa-web-bdd-tests`:
|
||||||
|
- включен сбор всех новых mp4 из Selenoid за прогон;
|
||||||
|
- видео прикладываются в Allure через external attachments.
|
||||||
|
|
||||||
|
Для `qa-playwright-tests`:
|
||||||
|
- по умолчанию используется `mcr.microsoft.com/playwright/java:v1.58.0-jammy` (`PLAYWRIGHT_DOCKER_IMAGE`).
|
||||||
|
|
||||||
|
Для `qa-mobile-appium-tests`:
|
||||||
|
- перед запуском синхронизируются `wiremock/mappings` и `wiremock/__files/wishlist.apk` в контейнер;
|
||||||
|
- проверяется доступность `APP_URL` изнутри эмулятора;
|
||||||
|
- сохраняются `project/target/mobile-debug/*`.
|
||||||
|
|
||||||
|
`qa-runner` завершает сборку как `UNSTABLE`, если падают только UI-контуры (`qa-selenium-tests`, `qa-web-bdd-tests`, `qa-playwright-tests`). Для остальных контуров падение блокирующее (`FAILURE`).
|
||||||
|
|
||||||
## Команды для повторного запуска с нуля
|
## Команды для повторного запуска с нуля
|
||||||
|
|
||||||
Снести все:
|
Снести все:
|
||||||
|
|
||||||
|
`bash`:
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/down.yml
|
ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/down.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`wsl`:
|
||||||
```powershell
|
```powershell
|
||||||
wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/hw8 && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/down.yml"
|
wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/projectwork && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/down.yml"
|
||||||
```
|
```
|
||||||
|
|
||||||
Полный reset + deploy:
|
Полный reset + deploy:
|
||||||
|
|
||||||
По умолчанию эта команда раскатывает профиль `full` (все job: DevOps + QA), так как в `ansible/playbooks/site.yml` задано `jobs_profile | default('full')`.
|
По умолчанию эта команда раскатывает профиль `full` (все job: DevOps + QA), так как в `ansible/playbooks/site.yml` задано `jobs_profile | default('full')`.
|
||||||
|
|
||||||
|
`bash`:
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/down.yml
|
ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/down.yml
|
||||||
ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml
|
ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`wsl`:
|
||||||
```powershell
|
```powershell
|
||||||
wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/hw8 && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/down.yml && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml"
|
wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/projectwork && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/down.yml && ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/site.yml"
|
||||||
```
|
```
|
||||||
|
|
||||||
Если нужен только профиль `devops`, добавьте `-e jobs_profile=devops` во вторую команду (`site.yml`).
|
Если нужен только профиль `devops`, добавьте `-e jobs_profile=devops` во вторую команду (`site.yml`).
|
||||||
@@ -157,7 +180,9 @@ wsl bash -lc "cd /mnt/c/Users/spawn/IdeaProjects/otus-autotests/hw8 && ansible-p
|
|||||||
## Где что лежит
|
## Где что лежит
|
||||||
|
|
||||||
- `ansible/` - playbook раскатки/удаления
|
- `ansible/` - playbook раскатки/удаления
|
||||||
- `compose/` - инфраструктура Jenkins/agents/registry/nginx
|
- `compose/` - инфраструктура Jenkins/agents/registry/nginx/selenoid
|
||||||
- `compose/images/` - Dockerfile для агентов и test-runner образов
|
- `compose/images/` - Dockerfile для агентов и test-runner образов
|
||||||
- `config/jobs/` - full JJB (DevOps + QA)
|
- `config/jobs/` - full JJB (DevOps + QA)
|
||||||
- `config/jobs-devops/` - JJB профиль только для DevOps job
|
- `config/jobs-devops/` - JJB профиль только для DevOps job
|
||||||
|
- `config/wiremock/` - маппинги для контрактных API тестов
|
||||||
|
- `contracts-tests/` - API контрактные тесты (JsonSchemaValidation)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
- name: Destroy OTUS HW8 Jenkins infrastructure
|
- name: Destroy OTUS ProjectWork Jenkins infrastructure
|
||||||
hosts: local
|
hosts: local
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
vars:
|
vars:
|
||||||
hw8_root: "{{ playbook_dir }}/../.."
|
project_root: "{{ playbook_dir }}/../.."
|
||||||
compose_dir: "{{ hw8_root }}/compose"
|
compose_dir: "{{ project_root }}/compose"
|
||||||
compose_file: "{{ compose_dir }}/docker-compose.yml"
|
compose_file: "{{ compose_dir }}/docker-compose.yml"
|
||||||
compose_env_file: "{{ compose_dir }}/.env"
|
compose_env_file: "{{ compose_dir }}/.env"
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy OTUS HW8 Jenkins infrastructure
|
- name: Deploy OTUS ProjectWork Jenkins infrastructure
|
||||||
hosts: local
|
hosts: local
|
||||||
gather_facts: true
|
gather_facts: true
|
||||||
vars:
|
vars:
|
||||||
hw8_root: "{{ playbook_dir }}/../.."
|
project_root: "{{ playbook_dir }}/../.."
|
||||||
compose_dir: "{{ hw8_root }}/compose"
|
compose_dir: "{{ project_root }}/compose"
|
||||||
compose_file: "{{ compose_dir }}/docker-compose.yml"
|
compose_file: "{{ compose_dir }}/docker-compose.yml"
|
||||||
compose_env_file: "{{ compose_dir }}/.env"
|
compose_env_file: "{{ compose_dir }}/.env"
|
||||||
|
selenoid_video_output_dir: "{{ compose_dir }}/selenoid/video"
|
||||||
jobs_profile_effective: "{{ jobs_profile | default('full') }}"
|
jobs_profile_effective: "{{ jobs_profile | default('full') }}"
|
||||||
jjb_paths:
|
jjb_paths:
|
||||||
full: /workspace/hw8/config/jobs
|
full: /workspace/projectwork/config/jobs
|
||||||
devops: /workspace/hw8/config/jobs-devops
|
devops: /workspace/projectwork/config/jobs-devops
|
||||||
jjb_upload_path: "{{ jjb_paths.get(jobs_profile_effective, jjb_paths.full) }}"
|
jjb_upload_path: "{{ jjb_paths.get(jobs_profile_effective, jjb_paths.full) }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
@@ -42,11 +43,13 @@
|
|||||||
delay: 20
|
delay: 20
|
||||||
until: pull_jenkins_base.rc == 0
|
until: pull_jenkins_base.rc == 0
|
||||||
|
|
||||||
- name: Build and start registry, jenkins and nginx
|
- name: Build and start core services (registry, jenkins, nginx, selenoid)
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} up -d --build registry jenkins nginx
|
cmd: docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} up -d --build registry jenkins nginx selenoid selenoid-ui
|
||||||
args:
|
args:
|
||||||
chdir: "{{ compose_dir }}"
|
chdir: "{{ compose_dir }}"
|
||||||
|
environment:
|
||||||
|
SELENOID_VIDEO_OUTPUT_DIR: "{{ selenoid_video_output_dir }}"
|
||||||
register: compose_bootstrap
|
register: compose_bootstrap
|
||||||
retries: 4
|
retries: 4
|
||||||
delay: 20
|
delay: 20
|
||||||
@@ -61,6 +64,23 @@
|
|||||||
delay: 5
|
delay: 5
|
||||||
until: jenkins_ready.status == 200
|
until: jenkins_ready.status == 200
|
||||||
|
|
||||||
|
- name: Wait for Selenoid to become available
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://127.0.0.1:4444/status"
|
||||||
|
status_code: 200
|
||||||
|
register: selenoid_ready
|
||||||
|
retries: 60
|
||||||
|
delay: 5
|
||||||
|
until: selenoid_ready.status == 200
|
||||||
|
|
||||||
|
- name: Pre-pull Selenoid browser and recorder images
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: docker pull {{ item }}
|
||||||
|
loop:
|
||||||
|
- selenoid/vnc:chrome_128.0
|
||||||
|
- selenoid/vnc:firefox_125.0
|
||||||
|
- selenoid/video-recorder:latest-release
|
||||||
|
|
||||||
- name: Build and push slave/test images to local registry
|
- name: Build and push slave/test images to local registry
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: ./scripts/build_and_push_images.sh localhost:5005 1.0.0
|
cmd: ./scripts/build_and_push_images.sh localhost:5005 1.0.0
|
||||||
@@ -76,12 +96,16 @@
|
|||||||
cmd: docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} up -d agent-maven agent-jjb
|
cmd: docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} up -d agent-maven agent-jjb
|
||||||
args:
|
args:
|
||||||
chdir: "{{ compose_dir }}"
|
chdir: "{{ compose_dir }}"
|
||||||
|
environment:
|
||||||
|
SELENOID_VIDEO_OUTPUT_DIR: "{{ selenoid_video_output_dir }}"
|
||||||
|
|
||||||
- name: Build jobs_uploader image with latest changes
|
- name: Build jobs_uploader image with latest changes
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} build jobs_uploader
|
cmd: docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} build jobs_uploader
|
||||||
args:
|
args:
|
||||||
chdir: "{{ compose_dir }}"
|
chdir: "{{ compose_dir }}"
|
||||||
|
environment:
|
||||||
|
SELENOID_VIDEO_OUTPUT_DIR: "{{ selenoid_video_output_dir }}"
|
||||||
|
|
||||||
- name: Remove stale jobs_uploader run containers
|
- name: Remove stale jobs_uploader run containers
|
||||||
ansible.builtin.shell: |
|
ansible.builtin.shell: |
|
||||||
@@ -97,6 +121,8 @@
|
|||||||
cmd: timeout 900 docker compose -f {{ compose_file }} --env-file {{ compose_env_file }} run --rm -e JJB_PATH={{ jjb_upload_path }} 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:
|
args:
|
||||||
chdir: "{{ compose_dir }}"
|
chdir: "{{ compose_dir }}"
|
||||||
|
environment:
|
||||||
|
SELENOID_VIDEO_OUTPUT_DIR: "{{ selenoid_video_output_dir }}"
|
||||||
|
|
||||||
- name: Show endpoint details
|
- name: Show endpoint details
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ services:
|
|||||||
- jenkins_home:/var/jenkins_home
|
- jenkins_home:/var/jenkins_home
|
||||||
- ./jenkins/casc:/var/jenkins_home/casc_configs:ro
|
- ./jenkins/casc:/var/jenkins_home/casc_configs:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ..:/workspace/hw8:ro
|
- ..:/workspace/projectwork:ro
|
||||||
|
- ../..:/workspace/otus:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/login >/dev/null"]
|
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/login >/dev/null"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
@@ -55,6 +56,60 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- jenkins_net
|
- jenkins_net
|
||||||
|
|
||||||
|
selenoid:
|
||||||
|
image: aerokube/selenoid:1.11.3
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
DOCKER_API_VERSION: "1.44"
|
||||||
|
OVERRIDE_VIDEO_OUTPUT_DIR: ${SELENOID_VIDEO_OUTPUT_DIR:-/opt/selenoid/video}
|
||||||
|
ports:
|
||||||
|
- "4444:4444"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./selenoid/browsers.json:/etc/selenoid/browsers.json:ro
|
||||||
|
- ./selenoid/video:/opt/selenoid/video
|
||||||
|
- ./selenoid/logs:/opt/selenoid/logs
|
||||||
|
command:
|
||||||
|
- "-conf"
|
||||||
|
- "/etc/selenoid/browsers.json"
|
||||||
|
- "-video-recorder-image"
|
||||||
|
- "selenoid/video-recorder:latest-release"
|
||||||
|
- "-container-network"
|
||||||
|
- "otus_jenkins_net"
|
||||||
|
- "-limit"
|
||||||
|
- "4"
|
||||||
|
- "-timeout"
|
||||||
|
- "5m"
|
||||||
|
- "-service-startup-timeout"
|
||||||
|
- "120s"
|
||||||
|
- "-session-attempt-timeout"
|
||||||
|
- "120s"
|
||||||
|
- "-video-output-dir"
|
||||||
|
- "/opt/selenoid/video"
|
||||||
|
- "-log-output-dir"
|
||||||
|
- "/opt/selenoid/logs"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1:4444/status >/dev/null 2>&1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 30
|
||||||
|
networks:
|
||||||
|
- jenkins_net
|
||||||
|
|
||||||
|
selenoid-ui:
|
||||||
|
image: aerokube/selenoid-ui:1.10.11
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
selenoid:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "8089:8080"
|
||||||
|
command:
|
||||||
|
- "--selenoid-uri"
|
||||||
|
- "http://selenoid:4444"
|
||||||
|
networks:
|
||||||
|
- jenkins_net
|
||||||
|
|
||||||
jobs_uploader:
|
jobs_uploader:
|
||||||
build:
|
build:
|
||||||
context: ./jobs_uploader
|
context: ./jobs_uploader
|
||||||
@@ -67,9 +122,10 @@ services:
|
|||||||
JENKINS_HOSTNAME: http://jenkins:8080
|
JENKINS_HOSTNAME: http://jenkins:8080
|
||||||
JENKINS_USERNAME: ${JENKINS_ADMIN_ID}
|
JENKINS_USERNAME: ${JENKINS_ADMIN_ID}
|
||||||
JENKINS_PASSWORD: ${JENKINS_ADMIN_PASSWORD}
|
JENKINS_PASSWORD: ${JENKINS_ADMIN_PASSWORD}
|
||||||
JJB_PATH: /workspace/hw8/config/jobs
|
JJB_PATH: /workspace/projectwork/config/jobs
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/workspace/hw8:ro
|
- ..:/workspace/projectwork:ro
|
||||||
|
- ../..:/workspace/otus:ro
|
||||||
networks:
|
networks:
|
||||||
- jenkins_net
|
- jenkins_net
|
||||||
|
|
||||||
@@ -87,10 +143,13 @@ services:
|
|||||||
JENKINS_AGENT_WORKDIR: /home/jenkins/agent
|
JENKINS_AGENT_WORKDIR: /home/jenkins/agent
|
||||||
JENKINS_WEB_SOCKET: "true"
|
JENKINS_WEB_SOCKET: "true"
|
||||||
JENKINS_LABELS: maven docker
|
JENKINS_LABELS: maven docker
|
||||||
HW8_ROOT: /workspace/hw8
|
OTUS_WORKSPACE_ROOT: /workspace/projectwork
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ..:/workspace/hw8:ro
|
- ..:/workspace/projectwork:ro
|
||||||
|
- ../..:/workspace/otus:ro
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
networks:
|
networks:
|
||||||
- jenkins_net
|
- jenkins_net
|
||||||
|
|
||||||
@@ -108,15 +167,19 @@ services:
|
|||||||
JENKINS_AGENT_WORKDIR: /home/jenkins/agent
|
JENKINS_AGENT_WORKDIR: /home/jenkins/agent
|
||||||
JENKINS_WEB_SOCKET: "true"
|
JENKINS_WEB_SOCKET: "true"
|
||||||
JENKINS_LABELS: jjb docker
|
JENKINS_LABELS: jjb docker
|
||||||
HW8_ROOT: /workspace/hw8
|
OTUS_WORKSPACE_ROOT: /workspace/projectwork
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ..:/workspace/hw8:ro
|
- ..:/workspace/projectwork:ro
|
||||||
|
- ../..:/workspace/otus:ro
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
networks:
|
networks:
|
||||||
- jenkins_net
|
- jenkins_net
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
jenkins_net:
|
jenkins_net:
|
||||||
|
name: otus_jenkins_net
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ jenkins:
|
|||||||
- key: JENKINS_URL_INTERNAL
|
- key: JENKINS_URL_INTERNAL
|
||||||
value: "http://jenkins:8080"
|
value: "http://jenkins:8080"
|
||||||
- key: OTUS_WORKSPACE_ROOT
|
- key: OTUS_WORKSPACE_ROOT
|
||||||
value: "/workspace/hw8"
|
value: "/workspace/projectwork"
|
||||||
- key: MOBILE_DB_PASSWORD
|
- key: MOBILE_DB_PASSWORD
|
||||||
value: "${MOBILE_DB_PASSWORD}"
|
value: "${MOBILE_DB_PASSWORD}"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"chrome": {
|
||||||
|
"default": "128.0",
|
||||||
|
"versions": {
|
||||||
|
"128.0": {
|
||||||
|
"image": "selenoid/vnc:chrome_128.0",
|
||||||
|
"port": "4444",
|
||||||
|
"path": "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"firefox": {
|
||||||
|
"default": "125.0",
|
||||||
|
"versions": {
|
||||||
|
"125.0": {
|
||||||
|
"image": "selenoid/vnc:firefox_125.0",
|
||||||
|
"port": "4444",
|
||||||
|
"path": "/wd/hub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
- defaults:
|
- defaults:
|
||||||
name: global
|
name: global
|
||||||
project_folder: /workspace/hw8
|
project_folder: /workspace/projectwork
|
||||||
test_image_tag: "1.0.0"
|
test_image_tag: "1.0.0"
|
||||||
build_keep: 40
|
build_keep: 40
|
||||||
|
|||||||
@@ -11,6 +11,6 @@
|
|||||||
parameters:
|
parameters:
|
||||||
- string:
|
- string:
|
||||||
name: JJB_PATH
|
name: JJB_PATH
|
||||||
default: /workspace/hw8/config/jobs
|
default: /workspace/projectwork/config/jobs
|
||||||
description: "Путь до JJB-конфигов"
|
description: "Путь до JJB-конфигов"
|
||||||
dsl: !include-raw-verbatim: ../../jobs/scripts/jobs-uploader.groovy
|
dsl: !include-raw-verbatim: ../../jobs/scripts/jobs-uploader.groovy
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
- defaults:
|
- defaults:
|
||||||
name: global
|
name: global
|
||||||
project_folder: /workspace/hw8
|
project_folder: /workspace/projectwork
|
||||||
test_image_tag: "1.0.0"
|
test_image_tag: "1.0.0"
|
||||||
build_keep: 40
|
build_keep: 40
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
- property:
|
- property:
|
||||||
name: hw8-build-policy
|
name: projectwork-build-policy
|
||||||
properties:
|
properties:
|
||||||
- build-discarder:
|
- build-discarder:
|
||||||
num-to-keep: 40
|
num-to-keep: 40
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ pipeline {
|
|||||||
set -eux
|
set -eux
|
||||||
docker version
|
docker version
|
||||||
curl -fsS http://registry:5000/v2/_catalog
|
curl -fsS http://registry:5000/v2/_catalog
|
||||||
|
curl -fsS http://host.docker.internal:4444/status
|
||||||
|
docker pull selenoid/vnc:chrome_128.0
|
||||||
|
docker pull selenoid/video-recorder:latest-release || docker pull selenoid/video-recorder:latest
|
||||||
docker pull localhost:5005/otus/test-selenium:1.0.0
|
docker pull localhost:5005/otus/test-selenium:1.0.0
|
||||||
docker pull localhost:5005/otus/test-api:1.0.0
|
docker pull localhost:5005/otus/test-api:1.0.0
|
||||||
docker pull localhost:5005/otus/test-mobile:1.0.0
|
docker pull localhost:5005/otus/test-mobile:1.0.0
|
||||||
@@ -33,8 +36,8 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -eux
|
set -eux
|
||||||
HW8_ROOT_PATH="${HW8_ROOT:-/workspace/hw8}"
|
PROJECT_ROOT_PATH="${OTUS_WORKSPACE_ROOT:-/workspace/projectwork}"
|
||||||
test -f "${HW8_ROOT_PATH}/config/jobs/global.yaml"
|
test -f "${PROJECT_ROOT_PATH}/config/jobs/global.yaml"
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -eux
|
set -eux
|
||||||
|
git config --global --add safe.directory '*' || true
|
||||||
rm -rf ./sources
|
rm -rf ./sources
|
||||||
git clone "${QA_REPO_URL}" ./sources
|
git clone "${QA_REPO_URL}" ./sources
|
||||||
git -C ./sources checkout "${QA_REPO_REF}"
|
git -C ./sources checkout "${QA_REPO_REF}"
|
||||||
@@ -40,7 +41,7 @@ pipeline {
|
|||||||
echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
} > ./artifacts/run-info.txt
|
} > ./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")"
|
CID="$(docker create localhost:5005/otus/test-api:1.0.0 bash -lc "set -e; cd /workspace; mvn -f citrus-tests/pom.xml -Dallure.results.directory=target/allure-results test")"
|
||||||
cleanup_container() {
|
cleanup_container() {
|
||||||
docker rm -f "${CID}" >/dev/null 2>&1 || true
|
docker rm -f "${CID}" >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
@@ -50,6 +51,53 @@ pipeline {
|
|||||||
docker start -a "${CID}"
|
docker start -a "${CID}"
|
||||||
TEST_RC=$?
|
TEST_RC=$?
|
||||||
docker cp "${CID}:/workspace/citrus-tests/target" "./artifacts/citrus-target" || true
|
docker cp "${CID}:/workspace/citrus-tests/target" "./artifacts/citrus-target" || true
|
||||||
|
mkdir -p ./artifacts/citrus-target/allure-results
|
||||||
|
if ! ls ./artifacts/citrus-target/allure-results/* >/dev/null 2>&1; then
|
||||||
|
EXTRA_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||||||
|
TS_MS="$(( $(date +%s) * 1000 ))"
|
||||||
|
STATUS="passed"
|
||||||
|
if [ "${TEST_RC}" -ne 0 ]; then
|
||||||
|
STATUS="failed"
|
||||||
|
fi
|
||||||
|
cat > "./artifacts/citrus-target/allure-results/${EXTRA_UUID}-result.json" <<EOF
|
||||||
|
{
|
||||||
|
"uuid": "${EXTRA_UUID}",
|
||||||
|
"historyId": "api-citrus-summary-${BUILD_NUMBER}",
|
||||||
|
"name": "API Citrus pipeline summary",
|
||||||
|
"fullName": "pipeline.ApiCitrusSummary",
|
||||||
|
"status": "${STATUS}",
|
||||||
|
"stage": "finished",
|
||||||
|
"start": ${TS_MS},
|
||||||
|
"stop": ${TS_MS},
|
||||||
|
"labels": [
|
||||||
|
{"name": "suite", "value": "Pipeline"},
|
||||||
|
{"name": "package", "value": "pipeline"},
|
||||||
|
{"name": "testClass", "value": "ApiCitrusSummary"},
|
||||||
|
{"name": "testMethod", "value": "run"}
|
||||||
|
],
|
||||||
|
"steps": [],
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "job=${JOB_NAME}"
|
||||||
|
echo "build=${BUILD_NUMBER}"
|
||||||
|
echo "repo.url=${QA_REPO_URL}"
|
||||||
|
echo "repo.ref=${QA_REPO_REF}"
|
||||||
|
echo "repo.sha=${GIT_SHA}"
|
||||||
|
} > ./artifacts/citrus-target/allure-results/environment.properties
|
||||||
|
cat > ./artifacts/citrus-target/allure-results/executor.json <<EOF
|
||||||
|
{
|
||||||
|
"name": "Jenkins",
|
||||||
|
"type": "jenkins",
|
||||||
|
"url": "${JENKINS_URL}",
|
||||||
|
"buildName": "${JOB_NAME} #${BUILD_NUMBER}",
|
||||||
|
"buildUrl": "${BUILD_URL}",
|
||||||
|
"reportUrl": "${BUILD_URL}allure",
|
||||||
|
"buildOrder": ${BUILD_NUMBER}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
trap - EXIT INT TERM
|
trap - EXIT INT TERM
|
||||||
docker rm -f "${CID}" || true
|
docker rm -f "${CID}" || true
|
||||||
exit "${TEST_RC}"
|
exit "${TEST_RC}"
|
||||||
@@ -59,6 +107,13 @@ pipeline {
|
|||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
always {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
allure commandline: 'allure', includeProperties: false, jdk: '', results: [[path: 'artifacts/citrus-target/allure-results']]
|
||||||
|
} catch (Exception ex) {
|
||||||
|
echo "Allure publisher unavailable: ${ex.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
junit allowEmptyResults: true, testResults: 'artifacts/citrus-target/surefire-reports/*.xml'
|
junit allowEmptyResults: true, testResults: 'artifacts/citrus-target/surefire-reports/*.xml'
|
||||||
archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/run-info.txt,artifacts/citrus-target/**'
|
archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/run-info.txt,artifacts/citrus-target/**'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
pipeline {
|
||||||
|
agent { label 'maven' }
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
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} contract-api"
|
||||||
|
currentBuild.description = "by=${env.RUN_TRIGGER_NAME}; scope=users-contract"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Contract API Tests') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eux
|
||||||
|
PROJECT_ROOT_PATH="${OTUS_WORKSPACE_ROOT:-/workspace/projectwork}"
|
||||||
|
WIREMOCK_DIR="${PROJECT_ROOT_PATH}/config/wiremock"
|
||||||
|
|
||||||
|
test -d "${WIREMOCK_DIR}"
|
||||||
|
test -f "${PROJECT_ROOT_PATH}/contracts-tests/pom.xml"
|
||||||
|
|
||||||
|
rm -rf ./artifacts
|
||||||
|
mkdir -p ./artifacts
|
||||||
|
|
||||||
|
WM_NAME="wiremock-contract-${BUILD_NUMBER}"
|
||||||
|
WM_CID="$(docker run -d --rm --name "${WM_NAME}" -p 18080:8080 wiremock/wiremock:3.9.1)"
|
||||||
|
WM_IP="$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "${WM_CID}")"
|
||||||
|
test -n "${WM_IP}"
|
||||||
|
docker exec "${WM_CID}" mkdir -p /home/wiremock/mappings
|
||||||
|
tar -C "${WIREMOCK_DIR}" -cf - mappings | docker cp - "${WM_CID}:/home/wiremock"
|
||||||
|
docker restart "${WM_CID}" >/dev/null
|
||||||
|
|
||||||
|
cleanup_all() {
|
||||||
|
docker rm -f "${WM_CID}" >/dev/null 2>&1 || true
|
||||||
|
docker rm -f "${CID:-}" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap cleanup_all EXIT INT TERM
|
||||||
|
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
if curl -fsS "http://${WM_IP}:8080/__admin/health" >/dev/null; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
curl -fsS "http://${WM_IP}:8080/__admin/health" >/dev/null
|
||||||
|
|
||||||
|
CID="$(docker create \
|
||||||
|
--add-host=host.docker.internal:host-gateway \
|
||||||
|
localhost:5005/otus/test-api:1.0.0 \
|
||||||
|
bash -lc "set -e; cd /workspace; mvn -f contracts-tests/pom.xml -Dallure.results.directory=target/allure-results -DbaseUrl=http://${WM_IP}:8080 test")"
|
||||||
|
tar -C "${PROJECT_ROOT_PATH}" -cf - contracts-tests | docker cp - "${CID}:/workspace"
|
||||||
|
set +e
|
||||||
|
docker start -a "${CID}"
|
||||||
|
TEST_RC=$?
|
||||||
|
docker cp "${CID}:/workspace/contracts-tests/target" "./artifacts" || true
|
||||||
|
mkdir -p ./artifacts/target/allure-results
|
||||||
|
if ! ls ./artifacts/target/allure-results/* >/dev/null 2>&1; then
|
||||||
|
EXTRA_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||||||
|
TS_MS="$(( $(date +%s) * 1000 ))"
|
||||||
|
STATUS="passed"
|
||||||
|
if [ "${TEST_RC}" -ne 0 ]; then
|
||||||
|
STATUS="failed"
|
||||||
|
fi
|
||||||
|
cat > "./artifacts/target/allure-results/${EXTRA_UUID}-result.json" <<EOF
|
||||||
|
{
|
||||||
|
"uuid": "${EXTRA_UUID}",
|
||||||
|
"historyId": "api-contract-summary-${BUILD_NUMBER}",
|
||||||
|
"name": "API contract pipeline summary",
|
||||||
|
"fullName": "pipeline.ApiContractSummary",
|
||||||
|
"status": "${STATUS}",
|
||||||
|
"stage": "finished",
|
||||||
|
"start": ${TS_MS},
|
||||||
|
"stop": ${TS_MS},
|
||||||
|
"labels": [
|
||||||
|
{"name": "suite", "value": "Pipeline"},
|
||||||
|
{"name": "package", "value": "pipeline"},
|
||||||
|
{"name": "testClass", "value": "ApiContractSummary"},
|
||||||
|
{"name": "testMethod", "value": "run"}
|
||||||
|
],
|
||||||
|
"steps": [],
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "job=${JOB_NAME}"
|
||||||
|
echo "build=${BUILD_NUMBER}"
|
||||||
|
echo "scope=users-contract"
|
||||||
|
echo "wiremock.url=http://${WM_IP}:8080"
|
||||||
|
} > ./artifacts/target/allure-results/environment.properties
|
||||||
|
cat > ./artifacts/target/allure-results/executor.json <<EOF
|
||||||
|
{
|
||||||
|
"name": "Jenkins",
|
||||||
|
"type": "jenkins",
|
||||||
|
"url": "${JENKINS_URL}",
|
||||||
|
"buildName": "${JOB_NAME} #${BUILD_NUMBER}",
|
||||||
|
"buildUrl": "${BUILD_URL}",
|
||||||
|
"reportUrl": "${BUILD_URL}allure",
|
||||||
|
"buildOrder": ${BUILD_NUMBER}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
set -e
|
||||||
|
exit "${TEST_RC}"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
allure commandline: 'allure', includeProperties: false, jdk: '', results: [[path: 'artifacts/target/allure-results']]
|
||||||
|
} catch (Exception ex) {
|
||||||
|
echo "Allure publisher unavailable: ${ex.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
junit allowEmptyResults: true, testResults: 'artifacts/target/surefire-reports/*.xml'
|
||||||
|
archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/target/**'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
pipeline {
|
||||||
|
agent { label 'maven' }
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
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-rest"
|
||||||
|
currentBuild.description = "by=${env.RUN_TRIGGER_NAME}; repo=${params.API_REST_REPO_URL}; ref=${params.API_REST_REPO_REF}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run API REST Tests') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eux
|
||||||
|
git config --global --add safe.directory '*' || true
|
||||||
|
rm -rf ./sources ./artifacts
|
||||||
|
mkdir -p ./artifacts
|
||||||
|
|
||||||
|
git clone "${API_REST_REPO_URL}" ./sources
|
||||||
|
TARGET_REF="${API_REST_REPO_REF}"
|
||||||
|
if git -C ./sources show-ref --verify --quiet "refs/remotes/origin/${TARGET_REF}"; then
|
||||||
|
git -C ./sources checkout -B "${TARGET_REF}" "origin/${TARGET_REF}"
|
||||||
|
else
|
||||||
|
DEFAULT_REF="$(git -C ./sources symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')"
|
||||||
|
echo "Requested ref '${TARGET_REF}' not found, fallback to default '${DEFAULT_REF}'"
|
||||||
|
TARGET_REF="${DEFAULT_REF}"
|
||||||
|
git -C ./sources checkout -B "${TARGET_REF}" "origin/${TARGET_REF}"
|
||||||
|
fi
|
||||||
|
GIT_SHA="$(git -C ./sources rev-parse --short HEAD)"
|
||||||
|
|
||||||
|
CID="$(docker create localhost:5005/otus/test-api:1.0.0 bash -lc "set -e; cd /workspace; mvn -Dmaven.compiler.release=${API_REST_JAVA_RELEASE} -Dmaven.compiler.source=${API_REST_JAVA_RELEASE} -Dmaven.compiler.target=${API_REST_JAVA_RELEASE} -Dallure.results.directory=target/allure-results ${API_REST_MAVEN_GOAL}")"
|
||||||
|
cleanup_container() {
|
||||||
|
docker rm -f "${CID}" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap cleanup_container EXIT INT TERM
|
||||||
|
tar -C ./sources -cf - . | docker cp - "${CID}:/workspace"
|
||||||
|
set +e
|
||||||
|
docker start -a "${CID}"
|
||||||
|
TEST_RC=$?
|
||||||
|
docker cp "${CID}:/workspace/target" "./artifacts" || true
|
||||||
|
mkdir -p ./artifacts/target/allure-results
|
||||||
|
if ! ls ./artifacts/target/allure-results/* >/dev/null 2>&1; then
|
||||||
|
EXTRA_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||||||
|
TS_MS="$(( $(date +%s) * 1000 ))"
|
||||||
|
STATUS="passed"
|
||||||
|
if [ "${TEST_RC}" -ne 0 ]; then
|
||||||
|
STATUS="failed"
|
||||||
|
fi
|
||||||
|
cat > "./artifacts/target/allure-results/${EXTRA_UUID}-result.json" <<EOF
|
||||||
|
{
|
||||||
|
"uuid": "${EXTRA_UUID}",
|
||||||
|
"historyId": "api-rest-summary-${BUILD_NUMBER}",
|
||||||
|
"name": "API REST pipeline summary",
|
||||||
|
"fullName": "pipeline.ApiRestSummary",
|
||||||
|
"status": "${STATUS}",
|
||||||
|
"stage": "finished",
|
||||||
|
"start": ${TS_MS},
|
||||||
|
"stop": ${TS_MS},
|
||||||
|
"labels": [
|
||||||
|
{"name": "suite", "value": "Pipeline"},
|
||||||
|
{"name": "package", "value": "pipeline"},
|
||||||
|
{"name": "testClass", "value": "ApiRestSummary"},
|
||||||
|
{"name": "testMethod", "value": "run"}
|
||||||
|
],
|
||||||
|
"steps": [],
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "repo=${API_REST_REPO_URL}"
|
||||||
|
echo "ref=${TARGET_REF}"
|
||||||
|
echo "sha=${GIT_SHA}"
|
||||||
|
echo "maven_goal=${API_REST_MAVEN_GOAL}"
|
||||||
|
echo "java_release=${API_REST_JAVA_RELEASE}"
|
||||||
|
} > ./artifacts/run-info.txt
|
||||||
|
{
|
||||||
|
echo "job=${JOB_NAME}"
|
||||||
|
echo "build=${BUILD_NUMBER}"
|
||||||
|
echo "repo.url=${API_REST_REPO_URL}"
|
||||||
|
echo "repo.ref=${TARGET_REF}"
|
||||||
|
echo "repo.sha=${GIT_SHA}"
|
||||||
|
echo "maven.goal=${API_REST_MAVEN_GOAL}"
|
||||||
|
} > ./artifacts/target/allure-results/environment.properties
|
||||||
|
cat > ./artifacts/target/allure-results/executor.json <<EOF
|
||||||
|
{
|
||||||
|
"name": "Jenkins",
|
||||||
|
"type": "jenkins",
|
||||||
|
"url": "${JENKINS_URL}",
|
||||||
|
"buildName": "${JOB_NAME} #${BUILD_NUMBER}",
|
||||||
|
"buildUrl": "${BUILD_URL}",
|
||||||
|
"reportUrl": "${BUILD_URL}allure",
|
||||||
|
"buildOrder": ${BUILD_NUMBER}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
docker rm -f "${CID}" || true
|
||||||
|
exit "${TEST_RC}"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
allure commandline: 'allure', includeProperties: false, jdk: '', results: [[path: 'artifacts/target/allure-results']]
|
||||||
|
} catch (Exception ex) {
|
||||||
|
echo "Allure publisher unavailable: ${ex.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
junit allowEmptyResults: true, testResults: 'artifacts/target/surefire-reports/*.xml'
|
||||||
|
archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/run-info.txt,artifacts/target/**'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
pipeline {
|
||||||
|
agent { label 'maven' }
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
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} maven-extra"
|
||||||
|
currentBuild.description = "by=${env.RUN_TRIGGER_NAME}; repo=${params.EXTRA_REPO_URL}; ref=${params.EXTRA_REPO_REF}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Maven Extra Tests') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eux
|
||||||
|
git config --global --add safe.directory '*' || true
|
||||||
|
rm -rf ./sources ./artifacts
|
||||||
|
mkdir -p ./artifacts
|
||||||
|
|
||||||
|
if [ "${EXTRA_REPO_URL#/}" != "${EXTRA_REPO_URL}" ] && [ ! -d "${EXTRA_REPO_URL}" ]; then
|
||||||
|
mkdir -p ./artifacts/target/allure-results
|
||||||
|
EXTRA_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||||||
|
TS_MS="$(( $(date +%s) * 1000 ))"
|
||||||
|
cat > "./artifacts/target/allure-results/${EXTRA_UUID}-result.json" <<EOF
|
||||||
|
{
|
||||||
|
"uuid": "${EXTRA_UUID}",
|
||||||
|
"historyId": "maven-extra-skipped-${BUILD_NUMBER}",
|
||||||
|
"name": "Maven extra pipeline summary",
|
||||||
|
"fullName": "pipeline.MavenExtraSummary",
|
||||||
|
"status": "skipped",
|
||||||
|
"stage": "finished",
|
||||||
|
"start": ${TS_MS},
|
||||||
|
"stop": ${TS_MS},
|
||||||
|
"labels": [
|
||||||
|
{"name": "suite", "value": "Pipeline"},
|
||||||
|
{"name": "package", "value": "pipeline"},
|
||||||
|
{"name": "testClass", "value": "MavenExtraSummary"},
|
||||||
|
{"name": "testMethod", "value": "run"}
|
||||||
|
],
|
||||||
|
"steps": [],
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
{
|
||||||
|
echo "job=${JOB_NAME}"
|
||||||
|
echo "build=${BUILD_NUMBER}"
|
||||||
|
echo "repo.url=${EXTRA_REPO_URL}"
|
||||||
|
echo "repo.ref=${EXTRA_REPO_REF}"
|
||||||
|
echo "status=SKIPPED_MISSING_REPO"
|
||||||
|
} > ./artifacts/target/allure-results/environment.properties
|
||||||
|
cat > ./artifacts/target/allure-results/executor.json <<EOF
|
||||||
|
{
|
||||||
|
"name": "Jenkins",
|
||||||
|
"type": "jenkins",
|
||||||
|
"url": "${JENKINS_URL}",
|
||||||
|
"buildName": "${JOB_NAME} #${BUILD_NUMBER}",
|
||||||
|
"buildUrl": "${BUILD_URL}",
|
||||||
|
"reportUrl": "${BUILD_URL}allure",
|
||||||
|
"buildOrder": ${BUILD_NUMBER}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
{
|
||||||
|
echo "repo=${EXTRA_REPO_URL}"
|
||||||
|
echo "ref=${EXTRA_REPO_REF}"
|
||||||
|
echo "status=SKIPPED_MISSING_REPO"
|
||||||
|
} > ./artifacts/run-info.txt
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git clone "${EXTRA_REPO_URL}" ./sources
|
||||||
|
git -C ./sources checkout "${EXTRA_REPO_REF}"
|
||||||
|
GIT_SHA="$(git -C ./sources rev-parse --short HEAD)"
|
||||||
|
|
||||||
|
CID="$(docker create localhost:5005/otus/test-api:1.0.0 bash -lc "set -e; cd /workspace; mvn -Dallure.results.directory=target/allure-results ${EXTRA_MAVEN_GOAL}")"
|
||||||
|
cleanup_container() {
|
||||||
|
docker rm -f "${CID}" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap cleanup_container EXIT INT TERM
|
||||||
|
tar -C ./sources -cf - . | docker cp - "${CID}:/workspace"
|
||||||
|
set +e
|
||||||
|
docker start -a "${CID}"
|
||||||
|
TEST_RC=$?
|
||||||
|
docker cp "${CID}:/workspace/target" "./artifacts" || true
|
||||||
|
mkdir -p ./artifacts/target/allure-results
|
||||||
|
if ! ls ./artifacts/target/allure-results/* >/dev/null 2>&1; then
|
||||||
|
EXTRA_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||||||
|
TS_MS="$(( $(date +%s) * 1000 ))"
|
||||||
|
STATUS="passed"
|
||||||
|
if [ "${TEST_RC}" -ne 0 ]; then
|
||||||
|
STATUS="failed"
|
||||||
|
fi
|
||||||
|
cat > "./artifacts/target/allure-results/${EXTRA_UUID}-result.json" <<EOF
|
||||||
|
{
|
||||||
|
"uuid": "${EXTRA_UUID}",
|
||||||
|
"historyId": "maven-extra-summary-${BUILD_NUMBER}",
|
||||||
|
"name": "Maven extra pipeline summary",
|
||||||
|
"fullName": "pipeline.MavenExtraSummary",
|
||||||
|
"status": "${STATUS}",
|
||||||
|
"stage": "finished",
|
||||||
|
"start": ${TS_MS},
|
||||||
|
"stop": ${TS_MS},
|
||||||
|
"labels": [
|
||||||
|
{"name": "suite", "value": "Pipeline"},
|
||||||
|
{"name": "package", "value": "pipeline"},
|
||||||
|
{"name": "testClass", "value": "MavenExtraSummary"},
|
||||||
|
{"name": "testMethod", "value": "run"}
|
||||||
|
],
|
||||||
|
"steps": [],
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "repo=${EXTRA_REPO_URL}"
|
||||||
|
echo "ref=${EXTRA_REPO_REF}"
|
||||||
|
echo "sha=${GIT_SHA}"
|
||||||
|
echo "maven_goal=${EXTRA_MAVEN_GOAL}"
|
||||||
|
} > ./artifacts/run-info.txt
|
||||||
|
{
|
||||||
|
echo "job=${JOB_NAME}"
|
||||||
|
echo "build=${BUILD_NUMBER}"
|
||||||
|
echo "repo.url=${EXTRA_REPO_URL}"
|
||||||
|
echo "repo.ref=${EXTRA_REPO_REF}"
|
||||||
|
echo "repo.sha=${GIT_SHA}"
|
||||||
|
echo "maven.goal=${EXTRA_MAVEN_GOAL}"
|
||||||
|
} > ./artifacts/target/allure-results/environment.properties
|
||||||
|
cat > ./artifacts/target/allure-results/executor.json <<EOF
|
||||||
|
{
|
||||||
|
"name": "Jenkins",
|
||||||
|
"type": "jenkins",
|
||||||
|
"url": "${JENKINS_URL}",
|
||||||
|
"buildName": "${JOB_NAME} #${BUILD_NUMBER}",
|
||||||
|
"buildUrl": "${BUILD_URL}",
|
||||||
|
"reportUrl": "${BUILD_URL}allure",
|
||||||
|
"buildOrder": ${BUILD_NUMBER}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
docker rm -f "${CID}" || true
|
||||||
|
exit "${TEST_RC}"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
allure commandline: 'allure', includeProperties: false, jdk: '', results: [[path: 'artifacts/target/allure-results']]
|
||||||
|
} catch (Exception ex) {
|
||||||
|
echo "Allure publisher unavailable: ${ex.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
junit allowEmptyResults: true, testResults: 'artifacts/target/surefire-reports/*.xml'
|
||||||
|
archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/run-info.txt,artifacts/target/**'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
pipeline {
|
||||||
|
agent { label 'maven' }
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
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} playwright/${params.PLAYWRIGHT_BROWSER}"
|
||||||
|
currentBuild.description = "by=${env.RUN_TRIGGER_NAME}; repo=${params.PLAYWRIGHT_REPO_URL}; ref=${params.PLAYWRIGHT_REPO_REF}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run Playwright Tests') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eux
|
||||||
|
git config --global --add safe.directory '*' || true
|
||||||
|
rm -rf ./sources ./artifacts
|
||||||
|
mkdir -p ./artifacts
|
||||||
|
|
||||||
|
git clone "${PLAYWRIGHT_REPO_URL}" ./sources
|
||||||
|
TARGET_REF="${PLAYWRIGHT_REPO_REF}"
|
||||||
|
if git -C ./sources show-ref --verify --quiet "refs/remotes/origin/${TARGET_REF}"; then
|
||||||
|
git -C ./sources checkout -B "${TARGET_REF}" "origin/${TARGET_REF}"
|
||||||
|
else
|
||||||
|
DEFAULT_REF="$(git -C ./sources symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')"
|
||||||
|
echo "Requested ref '${TARGET_REF}' not found, fallback to default '${DEFAULT_REF}'"
|
||||||
|
TARGET_REF="${DEFAULT_REF}"
|
||||||
|
git -C ./sources checkout -B "${TARGET_REF}" "origin/${TARGET_REF}"
|
||||||
|
fi
|
||||||
|
GIT_SHA="$(git -C ./sources rev-parse --short HEAD)"
|
||||||
|
TEST_IMAGE="${PLAYWRIGHT_DOCKER_IMAGE}"
|
||||||
|
docker pull "${TEST_IMAGE}" || true
|
||||||
|
|
||||||
|
CID="$(docker create \
|
||||||
|
--add-host=host.docker.internal:host-gateway \
|
||||||
|
"${TEST_IMAGE}" \
|
||||||
|
bash -lc "set -e; cd /tmp; mvn -Dheadless=${PLAYWRIGHT_HEADLESS} -Dbrowser=${PLAYWRIGHT_BROWSER} -DbaseUrl=${PLAYWRIGHT_BASE_URL} test")"
|
||||||
|
cleanup_container() {
|
||||||
|
docker rm -f "${CID}" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap cleanup_container EXIT INT TERM
|
||||||
|
tar -C ./sources -cf - . | docker cp - "${CID}:/tmp"
|
||||||
|
set +e
|
||||||
|
docker start -a "${CID}"
|
||||||
|
TEST_RC=$?
|
||||||
|
docker cp "${CID}:/tmp/target" "./artifacts/target" || true
|
||||||
|
docker cp "${CID}:/tmp/traces" "./artifacts/traces" || true
|
||||||
|
mkdir -p ./artifacts/target/allure-results
|
||||||
|
ATTACHMENT_COUNT=0
|
||||||
|
ATTACHMENTS_MANIFEST="./artifacts/target/allure-results/.external-attachments.txt"
|
||||||
|
: > "${ATTACHMENTS_MANIFEST}"
|
||||||
|
ATTACH_FILES="$(find ./artifacts -type f \\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.mp4' -o -iname '*.webm' -o -iname '*.zip' \\) 2>/dev/null || true)"
|
||||||
|
if [ -n "${ATTACH_FILES}" ]; then
|
||||||
|
while IFS= read -r attachment_file; do
|
||||||
|
if [ -z "${attachment_file}" ] || [ ! -f "${attachment_file}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ATTACHMENT_COUNT=$((ATTACHMENT_COUNT + 1))
|
||||||
|
ext="$(echo "${attachment_file}" | awk -F. '{print tolower($NF)}')"
|
||||||
|
mime="application/octet-stream"
|
||||||
|
case "${ext}" in
|
||||||
|
png) mime="image/png" ;;
|
||||||
|
jpg|jpeg) mime="image/jpeg" ;;
|
||||||
|
mp4) mime="video/mp4" ;;
|
||||||
|
webm) mime="video/webm" ;;
|
||||||
|
zip) mime="application/zip" ;;
|
||||||
|
esac
|
||||||
|
source_name="external-attachment-${ATTACHMENT_COUNT}.${ext}"
|
||||||
|
cp "${attachment_file}" "./artifacts/target/allure-results/${source_name}" || continue
|
||||||
|
safe_name="$(basename "${attachment_file}" | sed 's/"/\\"/g')"
|
||||||
|
printf '%s|%s|%s\n' "${safe_name}" "${mime}" "${source_name}" >> "${ATTACHMENTS_MANIFEST}"
|
||||||
|
done <<EOF
|
||||||
|
${ATTACH_FILES}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
if [ "${ATTACHMENT_COUNT}" -gt 0 ]; then
|
||||||
|
ATTACHMENTS_JSON="$(awk -F'|' 'BEGIN{first=1} {if(!first){printf(",")} first=0; gsub(/"/,"\\\"", $1); printf("{\\\"name\\\":\\\"%s\\\",\\\"type\\\":\\\"%s\\\",\\\"source\\\":\\\"%s\\\"}", $1, $2, $3)}' "${ATTACHMENTS_MANIFEST}")"
|
||||||
|
EXTRA_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||||||
|
TS_MS="$(( $(date +%s) * 1000 ))"
|
||||||
|
cat > "./artifacts/target/allure-results/${EXTRA_UUID}-result.json" <<EOF
|
||||||
|
{
|
||||||
|
"uuid": "${EXTRA_UUID}",
|
||||||
|
"historyId": "external-artifacts-${BUILD_NUMBER}",
|
||||||
|
"name": "Collected artifacts",
|
||||||
|
"fullName": "pipeline.CollectedArtifacts",
|
||||||
|
"status": "passed",
|
||||||
|
"stage": "finished",
|
||||||
|
"start": ${TS_MS},
|
||||||
|
"stop": ${TS_MS},
|
||||||
|
"labels": [
|
||||||
|
{"name": "suite", "value": "Pipeline Artifacts"},
|
||||||
|
{"name": "package", "value": "pipeline"},
|
||||||
|
{"name": "testClass", "value": "CollectedArtifacts"},
|
||||||
|
{"name": "testMethod", "value": "attach"}
|
||||||
|
],
|
||||||
|
"attachments": [${ATTACHMENTS_JSON}],
|
||||||
|
"steps": [],
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "repo=${PLAYWRIGHT_REPO_URL}"
|
||||||
|
echo "ref=${TARGET_REF}"
|
||||||
|
echo "sha=${GIT_SHA}"
|
||||||
|
echo "browser=${PLAYWRIGHT_BROWSER}"
|
||||||
|
echo "headless=${PLAYWRIGHT_HEADLESS}"
|
||||||
|
echo "base_url=${PLAYWRIGHT_BASE_URL}"
|
||||||
|
echo "docker_image=${TEST_IMAGE}"
|
||||||
|
} > ./artifacts/run-info.txt
|
||||||
|
{
|
||||||
|
echo "job=${JOB_NAME}"
|
||||||
|
echo "build=${BUILD_NUMBER}"
|
||||||
|
echo "repo.url=${PLAYWRIGHT_REPO_URL}"
|
||||||
|
echo "repo.ref=${TARGET_REF}"
|
||||||
|
echo "repo.sha=${GIT_SHA}"
|
||||||
|
echo "browser=${PLAYWRIGHT_BROWSER}"
|
||||||
|
echo "headless=${PLAYWRIGHT_HEADLESS}"
|
||||||
|
echo "base.url=${PLAYWRIGHT_BASE_URL}"
|
||||||
|
echo "docker.image=${TEST_IMAGE}"
|
||||||
|
} > ./artifacts/target/allure-results/environment.properties
|
||||||
|
cat > ./artifacts/target/allure-results/executor.json <<EOF
|
||||||
|
{
|
||||||
|
"name": "Jenkins",
|
||||||
|
"type": "jenkins",
|
||||||
|
"url": "${JENKINS_URL}",
|
||||||
|
"buildName": "${JOB_NAME} #${BUILD_NUMBER}",
|
||||||
|
"buildUrl": "${BUILD_URL}",
|
||||||
|
"reportUrl": "${BUILD_URL}allure",
|
||||||
|
"buildOrder": ${BUILD_NUMBER}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
docker rm -f "${CID}" || true
|
||||||
|
exit "${TEST_RC}"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
allure commandline: 'allure', includeProperties: false, jdk: '', results: [[path: 'artifacts/target/allure-results']]
|
||||||
|
} catch (Exception ex) {
|
||||||
|
echo "Allure publisher unavailable: ${ex.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
junit allowEmptyResults: true, testResults: 'artifacts/target/surefire-reports/*.xml'
|
||||||
|
archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/run-info.txt,artifacts/target/**,artifacts/traces/**'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -eux
|
set -eux
|
||||||
|
git config --global --add safe.directory '*' || true
|
||||||
rm -rf ./project
|
rm -rf ./project
|
||||||
git clone "${MOBILE_REPO_URL}" ./project
|
git clone "${MOBILE_REPO_URL}" ./project
|
||||||
git -C ./project checkout "${MOBILE_REPO_REF}"
|
git -C ./project checkout "${MOBILE_REPO_REF}"
|
||||||
@@ -68,8 +69,8 @@ pipeline {
|
|||||||
dir('project') {
|
dir('project') {
|
||||||
sh '''
|
sh '''
|
||||||
set -eux
|
set -eux
|
||||||
HW8_ROOT_PATH="${HW8_ROOT:-/workspace/hw8}"
|
PROJECT_ROOT_PATH="${OTUS_WORKSPACE_ROOT:-/workspace/projectwork}"
|
||||||
COMPOSE_FILE="${HW8_ROOT_PATH}/config/compose/mobile-ci.compose.yml"
|
COMPOSE_FILE="${PROJECT_ROOT_PATH}/config/compose/mobile-ci.compose.yml"
|
||||||
if docker compose version >/dev/null 2>&1; then
|
if docker compose version >/dev/null 2>&1; then
|
||||||
compose_cmd() { PROJECT_DIR="$PWD" docker compose -f "${COMPOSE_FILE}" "$@"; }
|
compose_cmd() { PROJECT_DIR="$PWD" docker compose -f "${COMPOSE_FILE}" "$@"; }
|
||||||
elif docker-compose version >/dev/null 2>&1; then
|
elif docker-compose version >/dev/null 2>&1; then
|
||||||
@@ -96,6 +97,42 @@ pipeline {
|
|||||||
|
|
||||||
compose_cmd down -v --remove-orphans || true
|
compose_cmd down -v --remove-orphans || true
|
||||||
compose_cmd up -d ${SERVICES}
|
compose_cmd up -d ${SERVICES}
|
||||||
|
WIREMOCK_CID="$(compose_cmd ps -q wiremock)"
|
||||||
|
if [ -z "${WIREMOCK_CID}" ]; then
|
||||||
|
echo "Wiremock container is not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "./wiremock/mappings/wishlist-apk.json" ] || [ ! -f "./wiremock/__files/wishlist.apk" ]; then
|
||||||
|
echo "Missing wiremock APK assets in repository checkout: ./wiremock/mappings/wishlist-apk.json or ./wiremock/__files/wishlist.apk"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
docker exec "${WIREMOCK_CID}" sh -lc 'rm -rf /home/wiremock/mappings /home/wiremock/__files && mkdir -p /home/wiremock/mappings /home/wiremock/__files'
|
||||||
|
tar -C ./wiremock -cf - . | docker exec -i "${WIREMOCK_CID}" sh -lc 'tar -C /home/wiremock -xf -'
|
||||||
|
docker restart "${WIREMOCK_CID}"
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}starting{{end}}' "${WIREMOCK_CID}" || true)"
|
||||||
|
if [ "${status}" = "healthy" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "${i}" -eq 30 ]; then
|
||||||
|
echo "Wiremock is not healthy after reload"
|
||||||
|
docker logs "${WIREMOCK_CID}" || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if echo "${APP_URL}" | grep -Eq 'https?://wiremock:8080'; then
|
||||||
|
APK_PATH="$(echo "${APP_URL}" | sed -E 's#https?://[^/]+##')"
|
||||||
|
if [ -z "${APK_PATH}" ]; then
|
||||||
|
APK_PATH="/wishlist.apk"
|
||||||
|
fi
|
||||||
|
if ! docker exec "${WIREMOCK_CID}" sh -lc "wget -qO- \"http://127.0.0.1:8080${APK_PATH}\" >/dev/null"; then
|
||||||
|
echo "Wiremock can't serve APK path ${APK_PATH}"
|
||||||
|
docker exec "${WIREMOCK_CID}" sh -lc 'ls -la /home/wiremock /home/wiremock/mappings /home/wiremock/__files' || true
|
||||||
|
docker logs "${WIREMOCK_CID}" || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
EMULATORS=""
|
EMULATORS=""
|
||||||
SELECTED=0
|
SELECTED=0
|
||||||
for service in android-emulator-1 android-emulator-2; do
|
for service in android-emulator-1 android-emulator-2; do
|
||||||
@@ -205,6 +242,39 @@ pipeline {
|
|||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if echo "${APP_URL}" | grep -Eq 'https?://wiremock:8080'; then
|
||||||
|
APK_PATH="$(echo "${APP_URL}" | sed -E 's#https?://[^/]+##')"
|
||||||
|
if [ -z "${APK_PATH}" ]; then
|
||||||
|
APK_PATH="/wishlist.apk"
|
||||||
|
fi
|
||||||
|
OLD_IFS="${IFS}"
|
||||||
|
IFS=','
|
||||||
|
for target in ${MOBILE_EMULATORS_VALUE}; do
|
||||||
|
service_name="$(echo "${target}" | cut -d'|' -f1)"
|
||||||
|
if [ -z "${service_name}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
emu_cid="$(docker ps -q --filter "name=mobileci-${service_name}" | head -n1 || true)"
|
||||||
|
if [ -z "${emu_cid}" ]; then
|
||||||
|
echo "Emulator container not found for service ${service_name}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ready="false"
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if docker exec "${emu_cid}" sh -lc "wget -qO- \"http://wiremock:8080${APK_PATH}\" >/dev/null"; then
|
||||||
|
ready="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [ "${ready}" != "true" ]; then
|
||||||
|
echo "APK URL is not reachable from ${service_name}: ${APP_URL}"
|
||||||
|
docker logs --tail 200 "${emu_cid}" || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
IFS="${OLD_IFS}"
|
||||||
|
fi
|
||||||
|
|
||||||
run_single_class() {
|
run_single_class() {
|
||||||
class_name="$1"
|
class_name="$1"
|
||||||
@@ -410,8 +480,8 @@ EOF
|
|||||||
dir('project') {
|
dir('project') {
|
||||||
sh '''
|
sh '''
|
||||||
set +e
|
set +e
|
||||||
HW8_ROOT_PATH="${HW8_ROOT:-/workspace/hw8}"
|
PROJECT_ROOT_PATH="${OTUS_WORKSPACE_ROOT:-/workspace/projectwork}"
|
||||||
COMPOSE_FILE="${HW8_ROOT_PATH}/config/compose/mobile-ci.compose.yml"
|
COMPOSE_FILE="${PROJECT_ROOT_PATH}/config/compose/mobile-ci.compose.yml"
|
||||||
if docker compose version >/dev/null 2>&1; then
|
if docker compose version >/dev/null 2>&1; then
|
||||||
compose_cmd() { PROJECT_DIR="$PWD" docker compose -f "${COMPOSE_FILE}" "$@"; }
|
compose_cmd() { PROJECT_DIR="$PWD" docker compose -f "${COMPOSE_FILE}" "$@"; }
|
||||||
elif docker-compose version >/dev/null 2>&1; then
|
elif docker-compose version >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -8,12 +8,24 @@ pipeline {
|
|||||||
stage('Prepare Metadata') {
|
stage('Prepare Metadata') {
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
|
def bddRefsRaw = (params.BDD_REFS ?: '').trim()
|
||||||
|
def bddRefs = bddRefsRaw
|
||||||
|
? bddRefsRaw.split(',').collect { it.trim() }.findAll { it }.unique()
|
||||||
|
: []
|
||||||
|
if (bddRefs.isEmpty() && (params.BDD_REPO_REF ?: '').trim()) {
|
||||||
|
bddRefs = [params.BDD_REPO_REF.trim()]
|
||||||
|
}
|
||||||
|
if (bddRefs.isEmpty()) {
|
||||||
|
bddRefs = ['main', 'homework_2']
|
||||||
|
}
|
||||||
|
env.BDD_REFS_EFFECTIVE = bddRefs.join(',')
|
||||||
|
|
||||||
wrap([$class: 'BuildUser']) {
|
wrap([$class: 'BuildUser']) {
|
||||||
env.RUN_TRIGGER_USER = env.BUILD_USER_ID ?: env.BUILD_USER ?: 'system'
|
env.RUN_TRIGGER_USER = env.BUILD_USER_ID ?: env.BUILD_USER ?: 'system'
|
||||||
env.RUN_TRIGGER_NAME = env.BUILD_USER ?: env.BUILD_USER_ID ?: 'system'
|
env.RUN_TRIGGER_NAME = env.BUILD_USER ?: env.BUILD_USER_ID ?: 'system'
|
||||||
}
|
}
|
||||||
currentBuild.displayName = "#${env.BUILD_NUMBER} runner"
|
currentBuild.displayName = "#${env.BUILD_NUMBER} runner"
|
||||||
currentBuild.description = "by=${env.RUN_TRIGGER_NAME}; qaRef=${params.QA_REPO_REF}; mobileRef=${params.MOBILE_REPO_REF}"
|
currentBuild.description = "by=${env.RUN_TRIGGER_NAME}; bddRefs=${env.BDD_REFS_EFFECTIVE}; qaRef=${params.QA_REPO_REF}; mobileRef=${params.MOBILE_REPO_REF}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +33,33 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
def fanout = [:]
|
def fanout = [:]
|
||||||
|
def bddRefs = env.BDD_REFS_EFFECTIVE
|
||||||
|
.split(',')
|
||||||
|
.collect { it.trim() }
|
||||||
|
.findAll { it }
|
||||||
|
if (bddRefs.isEmpty()) {
|
||||||
|
bddRefs = ['main', 'homework_2']
|
||||||
|
}
|
||||||
|
|
||||||
|
bddRefs.each { ref ->
|
||||||
|
def refValue = ref
|
||||||
|
def refKey = ref.replaceAll(/[^A-Za-z0-9_.-]/, '_')
|
||||||
|
def runFile = "downstream-web-bdd-${refKey}.txt"
|
||||||
|
fanout["web-bdd-${refKey}"] = {
|
||||||
|
def run = build job: 'qa-web-bdd-tests',
|
||||||
|
wait: true,
|
||||||
|
propagate: false,
|
||||||
|
parameters: [
|
||||||
|
string(name: 'BDD_REPO_URL', value: params.BDD_REPO_URL),
|
||||||
|
string(name: 'BDD_REPO_REF', value: refValue),
|
||||||
|
string(name: 'BROWSER', value: params.BROWSER),
|
||||||
|
string(name: 'BASE_URL', value: params.BASE_URL),
|
||||||
|
string(name: 'SELENOID_URL', value: params.SELENOID_URL),
|
||||||
|
string(name: 'HEADLESS', value: params.HEADLESS)
|
||||||
|
]
|
||||||
|
writeFile file: runFile, text: "${run.number}|${run.result}\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fanout['selenium'] = {
|
fanout['selenium'] = {
|
||||||
def run = build job: 'qa-selenium-tests',
|
def run = build job: 'qa-selenium-tests',
|
||||||
@@ -73,6 +112,50 @@ pipeline {
|
|||||||
writeFile file: 'downstream-api.txt', text: "${run.number}|${run.result}\n"
|
writeFile file: 'downstream-api.txt', text: "${run.number}|${run.result}\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fanout['api-contract'] = {
|
||||||
|
def run = build job: 'qa-api-contract-tests',
|
||||||
|
wait: true,
|
||||||
|
propagate: false
|
||||||
|
writeFile file: 'downstream-api-contract.txt', text: "${run.number}|${run.result}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fanout['api-rest'] = {
|
||||||
|
def run = build job: 'qa-api-rest-tests',
|
||||||
|
wait: true,
|
||||||
|
propagate: false,
|
||||||
|
parameters: [
|
||||||
|
string(name: 'API_REST_REPO_URL', value: params.API_REST_REPO_URL),
|
||||||
|
string(name: 'API_REST_REPO_REF', value: params.API_REST_REPO_REF)
|
||||||
|
]
|
||||||
|
writeFile file: 'downstream-api-rest.txt', text: "${run.number}|${run.result}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fanout['extra'] = {
|
||||||
|
def run = build job: 'qa-maven-extra-tests',
|
||||||
|
wait: true,
|
||||||
|
propagate: false,
|
||||||
|
parameters: [
|
||||||
|
string(name: 'EXTRA_REPO_URL', value: params.EXTRA_REPO_URL),
|
||||||
|
string(name: 'EXTRA_REPO_REF', value: params.EXTRA_REPO_REF)
|
||||||
|
]
|
||||||
|
writeFile file: 'downstream-extra.txt', text: "${run.number}|${run.result}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fanout['playwright'] = {
|
||||||
|
def run = build job: 'qa-playwright-tests',
|
||||||
|
wait: true,
|
||||||
|
propagate: false,
|
||||||
|
parameters: [
|
||||||
|
string(name: 'PLAYWRIGHT_REPO_URL', value: params.PLAYWRIGHT_REPO_URL),
|
||||||
|
string(name: 'PLAYWRIGHT_REPO_REF', value: params.PLAYWRIGHT_REPO_REF),
|
||||||
|
string(name: 'PLAYWRIGHT_BROWSER', value: params.PLAYWRIGHT_BROWSER),
|
||||||
|
string(name: 'PLAYWRIGHT_HEADLESS', value: params.PLAYWRIGHT_HEADLESS),
|
||||||
|
string(name: 'PLAYWRIGHT_BASE_URL', value: params.BASE_URL),
|
||||||
|
string(name: 'PLAYWRIGHT_DOCKER_IMAGE', value: params.PLAYWRIGHT_DOCKER_IMAGE)
|
||||||
|
]
|
||||||
|
writeFile file: 'downstream-playwright.txt', text: "${run.number}|${run.result}\n"
|
||||||
|
}
|
||||||
|
|
||||||
parallel fanout
|
parallel fanout
|
||||||
|
|
||||||
def parseRun = { String fileName ->
|
def parseRun = { String fileName ->
|
||||||
@@ -86,36 +169,97 @@ pipeline {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def bddRuns = [:]
|
||||||
|
bddRefs.each { ref ->
|
||||||
|
def refKey = ref.replaceAll(/[^A-Za-z0-9_.-]/, '_')
|
||||||
|
bddRuns[ref] = parseRun("downstream-web-bdd-${refKey}.txt")
|
||||||
|
}
|
||||||
def seleniumRun = parseRun('downstream-selenium.txt')
|
def seleniumRun = parseRun('downstream-selenium.txt')
|
||||||
def mobileRun = parseRun('downstream-mobile.txt')
|
def mobileRun = parseRun('downstream-mobile.txt')
|
||||||
def apiRun = parseRun('downstream-api.txt')
|
def apiRun = parseRun('downstream-api.txt')
|
||||||
|
def apiContractRun = parseRun('downstream-api-contract.txt')
|
||||||
|
def apiRestRun = parseRun('downstream-api-rest.txt')
|
||||||
|
def extraRun = parseRun('downstream-extra.txt')
|
||||||
|
def playwrightRun = parseRun('downstream-playwright.txt')
|
||||||
def lines = []
|
def lines = []
|
||||||
lines << "job=${env.JOB_NAME}"
|
lines << "job=${env.JOB_NAME}"
|
||||||
lines << "build=${env.BUILD_NUMBER}"
|
lines << "build=${env.BUILD_NUMBER}"
|
||||||
lines << "trigger_user=${env.RUN_TRIGGER_USER}"
|
lines << "trigger_user=${env.RUN_TRIGGER_USER}"
|
||||||
lines << "trigger_name=${env.RUN_TRIGGER_NAME}"
|
lines << "trigger_name=${env.RUN_TRIGGER_NAME}"
|
||||||
|
lines << "bdd_repo_url=${params.BDD_REPO_URL}"
|
||||||
|
lines << "bdd_refs=${bddRefs.join(',')}"
|
||||||
lines << "qa_repo_url=${params.QA_REPO_URL}"
|
lines << "qa_repo_url=${params.QA_REPO_URL}"
|
||||||
lines << "qa_repo_ref=${params.QA_REPO_REF}"
|
lines << "qa_repo_ref=${params.QA_REPO_REF}"
|
||||||
lines << "mobile_repo_url=${params.MOBILE_REPO_URL}"
|
lines << "mobile_repo_url=${params.MOBILE_REPO_URL}"
|
||||||
lines << "mobile_repo_ref=${params.MOBILE_REPO_REF}"
|
lines << "mobile_repo_ref=${params.MOBILE_REPO_REF}"
|
||||||
|
lines << "api_rest_repo_url=${params.API_REST_REPO_URL}"
|
||||||
|
lines << "api_rest_repo_ref=${params.API_REST_REPO_REF}"
|
||||||
|
lines << "extra_repo_url=${params.EXTRA_REPO_URL}"
|
||||||
|
lines << "extra_repo_ref=${params.EXTRA_REPO_REF}"
|
||||||
|
lines << "playwright_repo_url=${params.PLAYWRIGHT_REPO_URL}"
|
||||||
|
lines << "playwright_repo_ref=${params.PLAYWRIGHT_REPO_REF}"
|
||||||
|
lines << "playwright_docker_image=${params.PLAYWRIGHT_DOCKER_IMAGE}"
|
||||||
|
bddRefs.each { ref ->
|
||||||
|
def refKey = ref.replaceAll(/[^A-Za-z0-9_.-]/, '_')
|
||||||
|
def run = bddRuns[ref]
|
||||||
|
lines << "web_bdd_${refKey}_build=${run.number}"
|
||||||
|
lines << "web_bdd_${refKey}_result=${run.result}"
|
||||||
|
lines << "web_bdd_${refKey}_link=/job/qa-web-bdd-tests/${run.number}/"
|
||||||
|
lines << "web_bdd_${refKey}_link_abs=${env.JENKINS_URL}job/qa-web-bdd-tests/${run.number}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
def primaryBddRef = bddRefs[0]
|
||||||
|
def primaryBddRun = bddRuns[primaryBddRef]
|
||||||
|
lines << "web_bdd_build=${primaryBddRun.number}"
|
||||||
|
lines << "web_bdd_result=${primaryBddRun.result}"
|
||||||
lines << "selenium_build=${seleniumRun.number}"
|
lines << "selenium_build=${seleniumRun.number}"
|
||||||
lines << "selenium_result=${seleniumRun.result}"
|
lines << "selenium_result=${seleniumRun.result}"
|
||||||
lines << "mobile_build=${mobileRun.number}"
|
lines << "mobile_build=${mobileRun.number}"
|
||||||
lines << "mobile_result=${mobileRun.result}"
|
lines << "mobile_result=${mobileRun.result}"
|
||||||
lines << "api_build=${apiRun.number}"
|
lines << "api_build=${apiRun.number}"
|
||||||
lines << "api_result=${apiRun.result}"
|
lines << "api_result=${apiRun.result}"
|
||||||
|
lines << "api_contract_build=${apiContractRun.number}"
|
||||||
|
lines << "api_contract_result=${apiContractRun.result}"
|
||||||
|
lines << "api_rest_build=${apiRestRun.number}"
|
||||||
|
lines << "api_rest_result=${apiRestRun.result}"
|
||||||
|
lines << "extra_build=${extraRun.number}"
|
||||||
|
lines << "extra_result=${extraRun.result}"
|
||||||
|
lines << "playwright_build=${playwrightRun.number}"
|
||||||
|
lines << "playwright_result=${playwrightRun.result}"
|
||||||
|
lines << "web_bdd_link=/job/qa-web-bdd-tests/${primaryBddRun.number}/"
|
||||||
lines << "selenium_link=/job/qa-selenium-tests/${seleniumRun.number}/"
|
lines << "selenium_link=/job/qa-selenium-tests/${seleniumRun.number}/"
|
||||||
lines << "mobile_link=/job/qa-mobile-appium-tests/${mobileRun.number}/"
|
lines << "mobile_link=/job/qa-mobile-appium-tests/${mobileRun.number}/"
|
||||||
lines << "api_link=/job/qa-api-citrus-tests/${apiRun.number}/"
|
lines << "api_link=/job/qa-api-citrus-tests/${apiRun.number}/"
|
||||||
|
lines << "api_contract_link=/job/qa-api-contract-tests/${apiContractRun.number}/"
|
||||||
|
lines << "api_rest_link=/job/qa-api-rest-tests/${apiRestRun.number}/"
|
||||||
|
lines << "extra_link=/job/qa-maven-extra-tests/${extraRun.number}/"
|
||||||
|
lines << "playwright_link=/job/qa-playwright-tests/${playwrightRun.number}/"
|
||||||
|
lines << "web_bdd_link_abs=${env.JENKINS_URL}job/qa-web-bdd-tests/${primaryBddRun.number}/"
|
||||||
lines << "selenium_link_abs=${env.JENKINS_URL}job/qa-selenium-tests/${seleniumRun.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 << "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}/"
|
lines << "api_link_abs=${env.JENKINS_URL}job/qa-api-citrus-tests/${apiRun.number}/"
|
||||||
|
lines << "api_contract_link_abs=${env.JENKINS_URL}job/qa-api-contract-tests/${apiContractRun.number}/"
|
||||||
|
lines << "api_rest_link_abs=${env.JENKINS_URL}job/qa-api-rest-tests/${apiRestRun.number}/"
|
||||||
|
lines << "extra_link_abs=${env.JENKINS_URL}job/qa-maven-extra-tests/${extraRun.number}/"
|
||||||
|
lines << "playwright_link_abs=${env.JENKINS_URL}job/qa-playwright-tests/${playwrightRun.number}/"
|
||||||
writeFile file: 'runner-summary.txt', text: lines.join('\n') + '\n'
|
writeFile file: 'runner-summary.txt', text: lines.join('\n') + '\n'
|
||||||
archiveArtifacts allowEmptyArchive: false, artifacts: 'runner-summary.txt'
|
archiveArtifacts allowEmptyArchive: false, artifacts: 'runner-summary.txt'
|
||||||
|
|
||||||
def failed = [seleniumRun, mobileRun, apiRun].any { it.result != 'SUCCESS' }
|
def criticalRuns = [mobileRun, apiRun, apiContractRun, apiRestRun, extraRun]
|
||||||
if (failed) {
|
def optionalRuns = []
|
||||||
error("One or more downstream QA jobs failed. See runner-summary.txt for build links and statuses.")
|
optionalRuns << seleniumRun
|
||||||
|
optionalRuns.addAll(bddRuns.values())
|
||||||
|
optionalRuns << playwrightRun
|
||||||
|
|
||||||
|
def criticalFailed = criticalRuns.any { it.result != 'SUCCESS' }
|
||||||
|
def optionalFailed = optionalRuns.any { it.result != 'SUCCESS' }
|
||||||
|
|
||||||
|
if (criticalFailed) {
|
||||||
|
error("One or more critical downstream QA jobs failed. See runner-summary.txt for build links and statuses.")
|
||||||
|
}
|
||||||
|
if (optionalFailed) {
|
||||||
|
currentBuild.result = 'UNSTABLE'
|
||||||
|
echo "Optional downstream jobs have failures (web-bdd refs/playwright). See runner-summary.txt for details."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ pipeline {
|
|||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -eux
|
set -eux
|
||||||
|
git config --global --add safe.directory '*' || true
|
||||||
rm -rf ./sources
|
rm -rf ./sources
|
||||||
git clone "${QA_REPO_URL}" ./sources
|
git clone "${QA_REPO_URL}" ./sources
|
||||||
git -C ./sources checkout "${QA_REPO_REF}"
|
git -C ./sources checkout "${QA_REPO_REF}"
|
||||||
GIT_SHA="$(git -C ./sources rev-parse --short HEAD)"
|
GIT_SHA="$(git -C ./sources rev-parse --short HEAD)"
|
||||||
|
REMOTE_FACTORY="./sources/src/main/java/ru/kovbasa/driver/RemoteDriverFactory.java"
|
||||||
|
if [ -f "${REMOTE_FACTORY}" ] && ! grep -q "selenoid.video.enabled" "${REMOTE_FACTORY}"; then
|
||||||
|
sed -i 's/"enableVideo", false/"enableVideo", Boolean.parseBoolean(System.getProperty("selenoid.video.enabled", "false"))/' "${REMOTE_FACTORY}"
|
||||||
|
fi
|
||||||
if ! grep -q "<artifactId>allure-junit5</artifactId>" ./sources/pom.xml; then
|
if ! grep -q "<artifactId>allure-junit5</artifactId>" ./sources/pom.xml; then
|
||||||
awk -v ver="${ALLURE_ADAPTER_VERSION}" '
|
awk -v ver="${ALLURE_ADAPTER_VERSION}" '
|
||||||
index($0, "</dependencies>") && !done {
|
index($0, "</dependencies>") && !done {
|
||||||
@@ -64,11 +69,34 @@ pipeline {
|
|||||||
if [ "${BROWSER}" = "chrome" ]; then
|
if [ "${BROWSER}" = "chrome" ]; then
|
||||||
EXTRA_ARGS="-Dchrome.binary=/usr/bin/chromium"
|
EXTRA_ARGS="-Dchrome.binary=/usr/bin/chromium"
|
||||||
fi
|
fi
|
||||||
|
VIDEO_ARGS=""
|
||||||
|
SELENOID_BASE=""
|
||||||
|
SELENOID_VIDEO_BEFORE="./artifacts/selenoid-videos-before.txt"
|
||||||
|
SELENOID_VIDEO_AFTER="./artifacts/selenoid-videos-after.txt"
|
||||||
|
SELENOID_VIDEO_NEW="./artifacts/selenoid-videos-new.txt"
|
||||||
|
: > "${SELENOID_VIDEO_BEFORE}"
|
||||||
|
: > "${SELENOID_VIDEO_AFTER}"
|
||||||
|
: > "${SELENOID_VIDEO_NEW}"
|
||||||
|
if [ "${EXECUTION_MODE}" = "selenoid" ]; then
|
||||||
|
case "${BROWSER}" in
|
||||||
|
chrome) docker pull selenoid/vnc:chrome_128.0 || true ;;
|
||||||
|
firefox) docker pull selenoid/vnc:firefox_125.0 || true ;;
|
||||||
|
esac
|
||||||
|
docker pull selenoid/video-recorder:latest-release || docker pull selenoid/video-recorder:latest || true
|
||||||
|
VIDEO_ARGS="-Dselenoid.video.enabled=true"
|
||||||
|
if [ -n "${SELENOID_URL}" ]; then
|
||||||
|
SELENOID_BASE="${SELENOID_URL%/wd/hub}"
|
||||||
|
(curl -fsS "${SELENOID_BASE}/video/" || true) \
|
||||||
|
| tr '"' '\n' \
|
||||||
|
| grep -E '\\.mp4$' \
|
||||||
|
| sort -u > "${SELENOID_VIDEO_BEFORE}" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
CID="$(docker create \
|
CID="$(docker create \
|
||||||
--add-host=host.docker.internal:host-gateway \
|
--add-host=host.docker.internal:host-gateway \
|
||||||
localhost:5005/otus/test-selenium:1.0.0 \
|
localhost:5005/otus/test-selenium:1.0.0 \
|
||||||
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")"
|
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 ${VIDEO_ARGS} ${EXTRA_ARGS} test")"
|
||||||
cleanup_container() {
|
cleanup_container() {
|
||||||
docker rm -f "${CID}" >/dev/null 2>&1 || true
|
docker rm -f "${CID}" >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
@@ -79,6 +107,83 @@ pipeline {
|
|||||||
TEST_RC=$?
|
TEST_RC=$?
|
||||||
docker cp "${CID}:/workspace/target" "./artifacts" || true
|
docker cp "${CID}:/workspace/target" "./artifacts" || true
|
||||||
mkdir -p ./artifacts/target/allure-results
|
mkdir -p ./artifacts/target/allure-results
|
||||||
|
if [ "${EXECUTION_MODE}" = "selenoid" ] && [ -n "${SELENOID_URL}" ]; then
|
||||||
|
(curl -fsS "${SELENOID_BASE}/video/" || true) \
|
||||||
|
| tr '"' '\n' \
|
||||||
|
| grep -E '\\.mp4$' \
|
||||||
|
| sort -u > "${SELENOID_VIDEO_AFTER}" || true
|
||||||
|
comm -13 "${SELENOID_VIDEO_BEFORE}" "${SELENOID_VIDEO_AFTER}" > "${SELENOID_VIDEO_NEW}" || true
|
||||||
|
if [ ! -s "${SELENOID_VIDEO_NEW}" ]; then
|
||||||
|
cp "${SELENOID_VIDEO_AFTER}" "${SELENOID_VIDEO_NEW}" || true
|
||||||
|
fi
|
||||||
|
while IFS= read -r video_file; do
|
||||||
|
[ -n "${video_file}" ] || continue
|
||||||
|
downloaded=0
|
||||||
|
for i in $(seq 1 20); do
|
||||||
|
if curl -fsS "${SELENOID_BASE}/video/${video_file}" -o "./artifacts/target/${video_file}"; then
|
||||||
|
downloaded=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [ "${downloaded}" -ne 1 ]; then
|
||||||
|
echo "Failed to fetch Selenoid video: ${video_file}"
|
||||||
|
fi
|
||||||
|
done < "${SELENOID_VIDEO_NEW}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ATTACHMENT_COUNT=0
|
||||||
|
ATTACHMENTS_MANIFEST="./artifacts/target/allure-results/.external-attachments.txt"
|
||||||
|
: > "${ATTACHMENTS_MANIFEST}"
|
||||||
|
ATTACH_FILES="$(find ./artifacts -type f \\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.mp4' -o -iname '*.webm' \\) 2>/dev/null || true)"
|
||||||
|
if [ -n "${ATTACH_FILES}" ]; then
|
||||||
|
while IFS= read -r attachment_file; do
|
||||||
|
if [ -z "${attachment_file}" ] || [ ! -f "${attachment_file}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ATTACHMENT_COUNT=$((ATTACHMENT_COUNT + 1))
|
||||||
|
ext="$(echo "${attachment_file}" | awk -F. '{print tolower($NF)}')"
|
||||||
|
mime="application/octet-stream"
|
||||||
|
case "${ext}" in
|
||||||
|
png) mime="image/png" ;;
|
||||||
|
jpg|jpeg) mime="image/jpeg" ;;
|
||||||
|
mp4) mime="video/mp4" ;;
|
||||||
|
webm) mime="video/webm" ;;
|
||||||
|
esac
|
||||||
|
source_name="external-attachment-${ATTACHMENT_COUNT}.${ext}"
|
||||||
|
cp "${attachment_file}" "./artifacts/target/allure-results/${source_name}" || continue
|
||||||
|
safe_name="$(basename "${attachment_file}" | sed 's/"/\\"/g')"
|
||||||
|
printf '%s|%s|%s\n' "${safe_name}" "${mime}" "${source_name}" >> "${ATTACHMENTS_MANIFEST}"
|
||||||
|
done <<EOF
|
||||||
|
${ATTACH_FILES}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
if [ "${ATTACHMENT_COUNT}" -gt 0 ]; then
|
||||||
|
ATTACHMENTS_JSON="$(awk -F'|' 'BEGIN{first=1} {if(!first){printf(",")} first=0; gsub(/"/,"\\\"", $1); printf("{\\\"name\\\":\\\"%s\\\",\\\"type\\\":\\\"%s\\\",\\\"source\\\":\\\"%s\\\"}", $1, $2, $3)}' "${ATTACHMENTS_MANIFEST}")"
|
||||||
|
EXTRA_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||||||
|
TS_MS="$(( $(date +%s) * 1000 ))"
|
||||||
|
cat > "./artifacts/target/allure-results/${EXTRA_UUID}-result.json" <<EOF
|
||||||
|
{
|
||||||
|
"uuid": "${EXTRA_UUID}",
|
||||||
|
"historyId": "external-artifacts-${BUILD_NUMBER}",
|
||||||
|
"name": "Collected artifacts",
|
||||||
|
"fullName": "pipeline.CollectedArtifacts",
|
||||||
|
"status": "passed",
|
||||||
|
"stage": "finished",
|
||||||
|
"start": ${TS_MS},
|
||||||
|
"stop": ${TS_MS},
|
||||||
|
"labels": [
|
||||||
|
{"name": "suite", "value": "Pipeline Artifacts"},
|
||||||
|
{"name": "package", "value": "pipeline"},
|
||||||
|
{"name": "testClass", "value": "CollectedArtifacts"},
|
||||||
|
{"name": "testMethod", "value": "attach"}
|
||||||
|
],
|
||||||
|
"attachments": [${ATTACHMENTS_JSON}],
|
||||||
|
"steps": [],
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
{
|
{
|
||||||
echo "job=${JOB_NAME}"
|
echo "job=${JOB_NAME}"
|
||||||
echo "build=${BUILD_NUMBER}"
|
echo "build=${BUILD_NUMBER}"
|
||||||
@@ -91,6 +196,7 @@ pipeline {
|
|||||||
echo "headless=${HEADLESS}"
|
echo "headless=${HEADLESS}"
|
||||||
echo "execution.mode=${EXECUTION_MODE}"
|
echo "execution.mode=${EXECUTION_MODE}"
|
||||||
echo "base.url=${BASE_URL}"
|
echo "base.url=${BASE_URL}"
|
||||||
|
echo "video.selection=all-new-from-selenoid"
|
||||||
} > ./artifacts/target/allure-results/environment.properties
|
} > ./artifacts/target/allure-results/environment.properties
|
||||||
cat > ./artifacts/target/allure-results/executor.json <<EOF
|
cat > ./artifacts/target/allure-results/executor.json <<EOF
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
pipeline {
|
||||||
|
agent { label 'maven' }
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
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} bdd/${params.BDD_REPO_REF}"
|
||||||
|
currentBuild.description =
|
||||||
|
"by=${env.RUN_TRIGGER_NAME}; repo=${params.BDD_REPO_URL}; ref=${params.BDD_REPO_REF}; browser=${params.BROWSER}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Run WEB BDD Tests In Docker') {
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
set -eux
|
||||||
|
git config --global --add safe.directory '*' || true
|
||||||
|
rm -rf ./sources
|
||||||
|
git clone "${BDD_REPO_URL}" ./sources
|
||||||
|
TARGET_REF="${BDD_REPO_REF}"
|
||||||
|
if git -C ./sources show-ref --verify --quiet "refs/remotes/origin/${TARGET_REF}"; then
|
||||||
|
git -C ./sources checkout -B "${TARGET_REF}" "origin/${TARGET_REF}"
|
||||||
|
else
|
||||||
|
DEFAULT_REF="$(git -C ./sources symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')"
|
||||||
|
echo "Requested ref '${TARGET_REF}' not found, fallback to default '${DEFAULT_REF}'"
|
||||||
|
TARGET_REF="${DEFAULT_REF}"
|
||||||
|
git -C ./sources checkout -B "${TARGET_REF}" "origin/${TARGET_REF}"
|
||||||
|
fi
|
||||||
|
GIT_SHA="$(git -C ./sources rev-parse --short HEAD)"
|
||||||
|
CHROME_FACTORY="./sources/src/main/java/ru/kovbasa/driver/ChromeDriverFactory.java"
|
||||||
|
if [ -f "${CHROME_FACTORY}" ]; then
|
||||||
|
cat > "${CHROME_FACTORY}" <<'EOF'
|
||||||
|
package ru.kovbasa.driver;
|
||||||
|
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
|
import org.openqa.selenium.chrome.ChromeOptions;
|
||||||
|
import org.openqa.selenium.remote.RemoteWebDriver;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ChromeDriverFactory implements DriverFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebDriver createDriver() {
|
||||||
|
final ChromeOptions options = new ChromeOptions();
|
||||||
|
options.addArguments("--start-maximized");
|
||||||
|
options.addArguments("--disable-notifications");
|
||||||
|
final boolean headless = Boolean.parseBoolean(System.getProperty("selenide.headless", "false"));
|
||||||
|
if (headless) {
|
||||||
|
options.addArguments("--headless=new");
|
||||||
|
}
|
||||||
|
options.addArguments("--no-sandbox");
|
||||||
|
options.addArguments("--disable-dev-shm-usage");
|
||||||
|
options.addArguments("--disable-gpu");
|
||||||
|
options.addArguments("--window-size=1920,1080");
|
||||||
|
options.addArguments("--remote-allow-origins=*");
|
||||||
|
|
||||||
|
final String remoteUrl = System.getProperty("selenoid.url", "").trim();
|
||||||
|
if (!remoteUrl.isEmpty()) {
|
||||||
|
try {
|
||||||
|
options.setCapability("selenoid:options", Map.of(
|
||||||
|
"name", "bdd-ui-tests",
|
||||||
|
"enableVNC", true,
|
||||||
|
"enableVideo", Boolean.parseBoolean(System.getProperty("selenoid.video.enabled", "false")),
|
||||||
|
"env", List.of("TZ=UTC")
|
||||||
|
));
|
||||||
|
final URL url = URI.create(remoteUrl).toURL();
|
||||||
|
return new RemoteWebDriver(url, options);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new RuntimeException("Failed to create RemoteWebDriver for selenoid.url=" + remoteUrl, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ChromeDriver(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
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 "bdd_repo_url=${BDD_REPO_URL}"
|
||||||
|
echo "bdd_repo_ref=${TARGET_REF}"
|
||||||
|
echo "bdd_repo_sha=${GIT_SHA}"
|
||||||
|
echo "browser=${BROWSER}"
|
||||||
|
echo "headless=${HEADLESS}"
|
||||||
|
echo "base_url=${BASE_URL}"
|
||||||
|
echo "selenoid_url=${SELENOID_URL}"
|
||||||
|
echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
} > ./artifacts/run-info.txt
|
||||||
|
|
||||||
|
EXTRA_ARGS=""
|
||||||
|
if [ "${BROWSER}" = "chrome" ]; then
|
||||||
|
EXTRA_ARGS="-Dchrome.binary=/usr/bin/chromium"
|
||||||
|
fi
|
||||||
|
SELENOID_ARGS=""
|
||||||
|
SELENOID_BASE=""
|
||||||
|
SELENOID_VIDEO_BEFORE="./artifacts/selenoid-videos-before.txt"
|
||||||
|
SELENOID_VIDEO_AFTER="./artifacts/selenoid-videos-after.txt"
|
||||||
|
SELENOID_VIDEO_NEW="./artifacts/selenoid-videos-new.txt"
|
||||||
|
: > "${SELENOID_VIDEO_BEFORE}"
|
||||||
|
: > "${SELENOID_VIDEO_AFTER}"
|
||||||
|
: > "${SELENOID_VIDEO_NEW}"
|
||||||
|
if [ -n "${SELENOID_URL}" ]; then
|
||||||
|
case "${BROWSER}" in
|
||||||
|
chrome) docker pull selenoid/vnc:chrome_128.0 || true ;;
|
||||||
|
firefox) docker pull selenoid/vnc:firefox_125.0 || true ;;
|
||||||
|
esac
|
||||||
|
docker pull selenoid/video-recorder:latest-release || docker pull selenoid/video-recorder:latest || true
|
||||||
|
SELENOID_ARGS="-Dselenoid.video.enabled=true"
|
||||||
|
SELENOID_BASE="${SELENOID_URL%/wd/hub}"
|
||||||
|
(curl -fsS "${SELENOID_BASE}/video/" || true) \
|
||||||
|
| tr '"' '\n' \
|
||||||
|
| grep -E '\\.mp4$' \
|
||||||
|
| sort -u > "${SELENOID_VIDEO_BEFORE}" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
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 -Dallure.version=${ALLURE_ADAPTER_VERSION} -Dbrowser=${BROWSER} -Dbase.url=${BASE_URL} -Dselenoid.url=${SELENOID_URL} -Dselenide.headless=${HEADLESS} -Dallure.results.directory=target/allure-results ${SELENOID_ARGS} ${EXTRA_ARGS} test")"
|
||||||
|
cleanup_container() {
|
||||||
|
docker rm -f "${CID}" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap cleanup_container EXIT INT TERM
|
||||||
|
tar -C ./sources -cf - . | docker cp - "${CID}:/workspace"
|
||||||
|
set +e
|
||||||
|
docker start -a "${CID}"
|
||||||
|
TEST_RC=$?
|
||||||
|
docker cp "${CID}:/workspace/target" "./artifacts" || true
|
||||||
|
mkdir -p ./artifacts/target/allure-results
|
||||||
|
if [ -n "${SELENOID_URL}" ]; then
|
||||||
|
(curl -fsS "${SELENOID_BASE}/video/" || true) \
|
||||||
|
| tr '"' '\n' \
|
||||||
|
| grep -E '\\.mp4$' \
|
||||||
|
| sort -u > "${SELENOID_VIDEO_AFTER}" || true
|
||||||
|
comm -13 "${SELENOID_VIDEO_BEFORE}" "${SELENOID_VIDEO_AFTER}" > "${SELENOID_VIDEO_NEW}" || true
|
||||||
|
if [ ! -s "${SELENOID_VIDEO_NEW}" ]; then
|
||||||
|
cp "${SELENOID_VIDEO_AFTER}" "${SELENOID_VIDEO_NEW}" || true
|
||||||
|
fi
|
||||||
|
while IFS= read -r video_file; do
|
||||||
|
[ -n "${video_file}" ] || continue
|
||||||
|
downloaded=0
|
||||||
|
for i in $(seq 1 20); do
|
||||||
|
if curl -fsS "${SELENOID_BASE}/video/${video_file}" -o "./artifacts/target/${video_file}"; then
|
||||||
|
downloaded=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [ "${downloaded}" -ne 1 ]; then
|
||||||
|
echo "Failed to fetch Selenoid video: ${video_file}"
|
||||||
|
fi
|
||||||
|
done < "${SELENOID_VIDEO_NEW}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ATTACHMENT_COUNT=0
|
||||||
|
ATTACHMENTS_MANIFEST="./artifacts/target/allure-results/.external-attachments.txt"
|
||||||
|
: > "${ATTACHMENTS_MANIFEST}"
|
||||||
|
ATTACH_FILES="$(find ./artifacts -type f \\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.mp4' -o -iname '*.webm' \\) 2>/dev/null || true)"
|
||||||
|
if [ -n "${ATTACH_FILES}" ]; then
|
||||||
|
while IFS= read -r attachment_file; do
|
||||||
|
if [ -z "${attachment_file}" ] || [ ! -f "${attachment_file}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ATTACHMENT_COUNT=$((ATTACHMENT_COUNT + 1))
|
||||||
|
ext="$(echo "${attachment_file}" | awk -F. '{print tolower($NF)}')"
|
||||||
|
mime="application/octet-stream"
|
||||||
|
case "${ext}" in
|
||||||
|
png) mime="image/png" ;;
|
||||||
|
jpg|jpeg) mime="image/jpeg" ;;
|
||||||
|
mp4) mime="video/mp4" ;;
|
||||||
|
webm) mime="video/webm" ;;
|
||||||
|
esac
|
||||||
|
source_name="external-attachment-${ATTACHMENT_COUNT}.${ext}"
|
||||||
|
cp "${attachment_file}" "./artifacts/target/allure-results/${source_name}" || continue
|
||||||
|
safe_name="$(basename "${attachment_file}" | sed 's/"/\\"/g')"
|
||||||
|
printf '%s|%s|%s\n' "${safe_name}" "${mime}" "${source_name}" >> "${ATTACHMENTS_MANIFEST}"
|
||||||
|
done <<EOF
|
||||||
|
${ATTACH_FILES}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
if [ "${ATTACHMENT_COUNT}" -gt 0 ]; then
|
||||||
|
ATTACHMENTS_JSON="$(awk -F'|' 'BEGIN{first=1} {if(!first){printf(",")} first=0; gsub(/"/,"\\\"", $1); printf("{\\\"name\\\":\\\"%s\\\",\\\"type\\\":\\\"%s\\\",\\\"source\\\":\\\"%s\\\"}", $1, $2, $3)}' "${ATTACHMENTS_MANIFEST}")"
|
||||||
|
EXTRA_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
||||||
|
TS_MS="$(( $(date +%s) * 1000 ))"
|
||||||
|
cat > "./artifacts/target/allure-results/${EXTRA_UUID}-result.json" <<EOF
|
||||||
|
{
|
||||||
|
"uuid": "${EXTRA_UUID}",
|
||||||
|
"historyId": "external-artifacts-${BUILD_NUMBER}",
|
||||||
|
"name": "Collected artifacts",
|
||||||
|
"fullName": "pipeline.CollectedArtifacts",
|
||||||
|
"status": "passed",
|
||||||
|
"stage": "finished",
|
||||||
|
"start": ${TS_MS},
|
||||||
|
"stop": ${TS_MS},
|
||||||
|
"labels": [
|
||||||
|
{"name": "suite", "value": "Pipeline Artifacts"},
|
||||||
|
{"name": "package", "value": "pipeline"},
|
||||||
|
{"name": "testClass", "value": "CollectedArtifacts"},
|
||||||
|
{"name": "testMethod", "value": "attach"}
|
||||||
|
],
|
||||||
|
"attachments": [${ATTACHMENTS_JSON}],
|
||||||
|
"steps": [],
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "job=${JOB_NAME}"
|
||||||
|
echo "build=${BUILD_NUMBER}"
|
||||||
|
echo "trigger.user=${RUN_TRIGGER_USER}"
|
||||||
|
echo "trigger.name=${RUN_TRIGGER_NAME}"
|
||||||
|
echo "repo.url=${BDD_REPO_URL}"
|
||||||
|
echo "repo.ref=${TARGET_REF}"
|
||||||
|
echo "repo.sha=${GIT_SHA}"
|
||||||
|
echo "browser=${BROWSER}"
|
||||||
|
echo "headless=${HEADLESS}"
|
||||||
|
echo "base.url=${BASE_URL}"
|
||||||
|
echo "selenoid.url=${SELENOID_URL}"
|
||||||
|
echo "video.selection=all-new-from-selenoid"
|
||||||
|
} > ./artifacts/target/allure-results/environment.properties
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
docker rm -f "${CID}" || true
|
||||||
|
exit "${TEST_RC}"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
script {
|
||||||
|
try {
|
||||||
|
allure commandline: 'allure', includeProperties: false, jdk: '', results: [[path: 'artifacts/target/allure-results']]
|
||||||
|
} catch (Exception ex) {
|
||||||
|
echo "Allure publisher unavailable: ${ex.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
junit allowEmptyResults: true, testResults: 'artifacts/target/surefire-reports/*.xml'
|
||||||
|
archiveArtifacts allowEmptyArchive: true, artifacts: 'artifacts/run-info.txt,artifacts/target/**'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,6 @@
|
|||||||
parameters:
|
parameters:
|
||||||
- string:
|
- string:
|
||||||
name: JJB_PATH
|
name: JJB_PATH
|
||||||
default: /workspace/hw8/config/jobs
|
default: /workspace/projectwork/config/jobs
|
||||||
description: "Путь до JJB-конфигов"
|
description: "Путь до JJB-конфигов"
|
||||||
dsl: !include-raw-verbatim: ../scripts/jobs-uploader.groovy
|
dsl: !include-raw-verbatim: ../scripts/jobs-uploader.groovy
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
- job:
|
||||||
|
name: qa-api-contract-tests
|
||||||
|
description: "Контрактные API тесты (WireMock + JsonSchemaValidation)."
|
||||||
|
project-type: pipeline
|
||||||
|
concurrent: true
|
||||||
|
sandbox: true
|
||||||
|
properties:
|
||||||
|
- build-discarder:
|
||||||
|
num-to-keep: 50
|
||||||
|
dsl: !include-raw-verbatim: ../scripts/qa-api-contract-tests.groovy
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
- job:
|
||||||
|
name: qa-api-rest-tests
|
||||||
|
description: "API REST тесты (RestAssured + JsonSchema + quality gates)."
|
||||||
|
project-type: pipeline
|
||||||
|
concurrent: true
|
||||||
|
sandbox: true
|
||||||
|
properties:
|
||||||
|
- build-discarder:
|
||||||
|
num-to-keep: 50
|
||||||
|
parameters:
|
||||||
|
- string:
|
||||||
|
name: API_REST_REPO_URL
|
||||||
|
default: https://git.kovbasa.ru/otus-autotests/hw3.git
|
||||||
|
- string:
|
||||||
|
name: API_REST_REPO_REF
|
||||||
|
default: main
|
||||||
|
- string:
|
||||||
|
name: API_REST_MAVEN_GOAL
|
||||||
|
default: verify
|
||||||
|
- string:
|
||||||
|
name: API_REST_JAVA_RELEASE
|
||||||
|
default: "21"
|
||||||
|
dsl: !include-raw-verbatim: ../scripts/qa-hw3-api-tests.groovy
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
- job:
|
||||||
|
name: qa-maven-extra-tests
|
||||||
|
description: "Дополнительные Maven тесты (универсальная job)."
|
||||||
|
project-type: pipeline
|
||||||
|
concurrent: true
|
||||||
|
sandbox: true
|
||||||
|
properties:
|
||||||
|
- build-discarder:
|
||||||
|
num-to-keep: 50
|
||||||
|
parameters:
|
||||||
|
- string:
|
||||||
|
name: EXTRA_REPO_URL
|
||||||
|
default: https://git.kovbasa.ru/otus-autotests/hw3.git
|
||||||
|
- string:
|
||||||
|
name: EXTRA_REPO_REF
|
||||||
|
default: homework_5
|
||||||
|
- string:
|
||||||
|
name: EXTRA_MAVEN_GOAL
|
||||||
|
default: test
|
||||||
|
dsl: !include-raw-verbatim: ../scripts/qa-hw5-tests.groovy
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
- job:
|
||||||
|
name: qa-playwright-tests
|
||||||
|
description: "Playwright UI тесты."
|
||||||
|
project-type: pipeline
|
||||||
|
concurrent: true
|
||||||
|
sandbox: true
|
||||||
|
properties:
|
||||||
|
- build-discarder:
|
||||||
|
num-to-keep: 50
|
||||||
|
parameters:
|
||||||
|
- string:
|
||||||
|
name: PLAYWRIGHT_REPO_URL
|
||||||
|
default: https://git.kovbasa.ru/otus-autotests/homework_6.git
|
||||||
|
- string:
|
||||||
|
name: PLAYWRIGHT_REPO_REF
|
||||||
|
default: master
|
||||||
|
- choice:
|
||||||
|
name: PLAYWRIGHT_BROWSER
|
||||||
|
choices:
|
||||||
|
- chromium
|
||||||
|
- firefox
|
||||||
|
- webkit
|
||||||
|
- choice:
|
||||||
|
name: PLAYWRIGHT_HEADLESS
|
||||||
|
choices:
|
||||||
|
- "true"
|
||||||
|
- "false"
|
||||||
|
- string:
|
||||||
|
name: PLAYWRIGHT_BASE_URL
|
||||||
|
default: https://otus.ru
|
||||||
|
- string:
|
||||||
|
name: PLAYWRIGHT_DOCKER_IMAGE
|
||||||
|
default: mcr.microsoft.com/playwright/java:v1.58.0-jammy
|
||||||
|
dsl: !include-raw-verbatim: ../scripts/qa-hw6-playwright-tests.groovy
|
||||||
@@ -5,10 +5,22 @@
|
|||||||
project-type: pipeline
|
project-type: pipeline
|
||||||
concurrent: true
|
concurrent: true
|
||||||
sandbox: true
|
sandbox: true
|
||||||
|
triggers:
|
||||||
|
- timed: "15 1 * * *"
|
||||||
properties:
|
properties:
|
||||||
- build-discarder:
|
- build-discarder:
|
||||||
num-to-keep: 50
|
num-to-keep: 50
|
||||||
parameters:
|
parameters:
|
||||||
|
- string:
|
||||||
|
name: BDD_REPO_URL
|
||||||
|
default: https://git.kovbasa.ru/otus-autotests/homework_1.git
|
||||||
|
- string:
|
||||||
|
name: BDD_REFS
|
||||||
|
default: main,homework_2
|
||||||
|
- string:
|
||||||
|
name: BDD_REPO_REF
|
||||||
|
default: homework_2
|
||||||
|
description: "Legacy fallback if BDD_REFS is empty"
|
||||||
- string:
|
- string:
|
||||||
name: QA_REPO_URL
|
name: QA_REPO_URL
|
||||||
default: https://git.kovbasa.ru/otus-autotests/homework_4.git
|
default: https://git.kovbasa.ru/otus-autotests/homework_4.git
|
||||||
@@ -41,15 +53,15 @@
|
|||||||
default: https://otus.ru
|
default: https://otus.ru
|
||||||
- string:
|
- string:
|
||||||
name: EXECUTION_MODE
|
name: EXECUTION_MODE
|
||||||
default: local
|
default: selenoid
|
||||||
- string:
|
- string:
|
||||||
name: SELENOID_URL
|
name: SELENOID_URL
|
||||||
default: http://host.docker.internal:4444/wd/hub
|
default: http://host.docker.internal:4444/wd/hub
|
||||||
- choice:
|
- choice:
|
||||||
name: HEADLESS
|
name: HEADLESS
|
||||||
choices:
|
choices:
|
||||||
- "true"
|
|
||||||
- "false"
|
- "false"
|
||||||
|
- "true"
|
||||||
- string:
|
- string:
|
||||||
name: APP_URL
|
name: APP_URL
|
||||||
default: http://wiremock:8080/wishlist.apk
|
default: http://wiremock:8080/wishlist.apk
|
||||||
@@ -83,4 +95,36 @@
|
|||||||
- string:
|
- string:
|
||||||
name: RESERVATION_OWNER
|
name: RESERVATION_OWNER
|
||||||
default: user4us
|
default: user4us
|
||||||
|
- string:
|
||||||
|
name: API_REST_REPO_URL
|
||||||
|
default: https://git.kovbasa.ru/otus-autotests/hw3.git
|
||||||
|
- string:
|
||||||
|
name: API_REST_REPO_REF
|
||||||
|
default: main
|
||||||
|
- string:
|
||||||
|
name: EXTRA_REPO_URL
|
||||||
|
default: https://git.kovbasa.ru/otus-autotests/hw3.git
|
||||||
|
- string:
|
||||||
|
name: EXTRA_REPO_REF
|
||||||
|
default: homework_5
|
||||||
|
- string:
|
||||||
|
name: PLAYWRIGHT_REPO_URL
|
||||||
|
default: https://git.kovbasa.ru/otus-autotests/homework_6.git
|
||||||
|
- string:
|
||||||
|
name: PLAYWRIGHT_REPO_REF
|
||||||
|
default: master
|
||||||
|
- choice:
|
||||||
|
name: PLAYWRIGHT_BROWSER
|
||||||
|
choices:
|
||||||
|
- chromium
|
||||||
|
- firefox
|
||||||
|
- webkit
|
||||||
|
- choice:
|
||||||
|
name: PLAYWRIGHT_HEADLESS
|
||||||
|
choices:
|
||||||
|
- "true"
|
||||||
|
- "false"
|
||||||
|
- string:
|
||||||
|
name: PLAYWRIGHT_DOCKER_IMAGE
|
||||||
|
default: mcr.microsoft.com/playwright/java:v1.58.0-jammy
|
||||||
dsl: !include-raw-verbatim: ../scripts/qa-runner.groovy
|
dsl: !include-raw-verbatim: ../scripts/qa-runner.groovy
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
description: "Базовый URL тестируемого сайта"
|
description: "Базовый URL тестируемого сайта"
|
||||||
- string:
|
- string:
|
||||||
name: EXECUTION_MODE
|
name: EXECUTION_MODE
|
||||||
default: local
|
default: selenoid
|
||||||
description: "Режим запуска (local|selenoid)"
|
description: "Режим запуска (local|selenoid)"
|
||||||
- string:
|
- string:
|
||||||
name: SELENOID_URL
|
name: SELENOID_URL
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
- choice:
|
- choice:
|
||||||
name: HEADLESS
|
name: HEADLESS
|
||||||
choices:
|
choices:
|
||||||
- "true"
|
|
||||||
- "false"
|
- "false"
|
||||||
|
- "true"
|
||||||
description: "Headless режим"
|
description: "Headless режим"
|
||||||
dsl: !include-raw-verbatim: ../scripts/qa-selenium-tests.groovy
|
dsl: !include-raw-verbatim: ../scripts/qa-selenium-tests.groovy
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
- job:
|
||||||
|
name: qa-web-bdd-tests
|
||||||
|
description: "WEB BDD/Cucumber тесты (homework_1, branch homework_2)."
|
||||||
|
project-type: pipeline
|
||||||
|
concurrent: true
|
||||||
|
sandbox: true
|
||||||
|
properties:
|
||||||
|
- build-discarder:
|
||||||
|
num-to-keep: 50
|
||||||
|
parameters:
|
||||||
|
- string:
|
||||||
|
name: BDD_REPO_URL
|
||||||
|
default: https://git.kovbasa.ru/otus-autotests/homework_1.git
|
||||||
|
description: "Git URL репозитория с BDD/Cucumber web тестами"
|
||||||
|
- string:
|
||||||
|
name: BDD_REPO_REF
|
||||||
|
default: homework_2
|
||||||
|
description: "Git branch/tag/commit для checkout"
|
||||||
|
- string:
|
||||||
|
name: ALLURE_ADAPTER_VERSION
|
||||||
|
default: 2.29.1
|
||||||
|
description: "Версия allure-junit5 адаптера"
|
||||||
|
- choice:
|
||||||
|
name: BROWSER
|
||||||
|
choices:
|
||||||
|
- chrome
|
||||||
|
- firefox
|
||||||
|
- string:
|
||||||
|
name: BASE_URL
|
||||||
|
default: https://otus.ru
|
||||||
|
- string:
|
||||||
|
name: SELENOID_URL
|
||||||
|
default: http://host.docker.internal:4444/wd/hub
|
||||||
|
- choice:
|
||||||
|
name: HEADLESS
|
||||||
|
choices:
|
||||||
|
- "false"
|
||||||
|
- "true"
|
||||||
|
dsl: !include-raw-verbatim: ../scripts/qa-web-bdd-tests.groovy
|
||||||
@@ -41,9 +41,14 @@
|
|||||||
filter-executors: true
|
filter-executors: true
|
||||||
filter-queue: true
|
filter-queue: true
|
||||||
job-name:
|
job-name:
|
||||||
|
- qa-web-bdd-tests
|
||||||
- qa-selenium-tests
|
- qa-selenium-tests
|
||||||
- qa-api-citrus-tests
|
- qa-api-citrus-tests
|
||||||
|
- qa-api-contract-tests
|
||||||
- qa-mobile-appium-tests
|
- qa-mobile-appium-tests
|
||||||
|
- qa-api-rest-tests
|
||||||
|
- qa-maven-extra-tests
|
||||||
|
- qa-playwright-tests
|
||||||
columns:
|
columns:
|
||||||
- status
|
- status
|
||||||
- weather
|
- weather
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"urlPath": "/users/500"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 500,
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
"jsonBody": {
|
||||||
|
"error": "temporary_failure",
|
||||||
|
"message": "Mocked failing method for resilience checks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"urlPath": "/users/1"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
"jsonBody": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Student 1",
|
||||||
|
"grade": "A",
|
||||||
|
"school_name": "OTUS School",
|
||||||
|
"city": "Moscow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"urlPath": "/users/get/all"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
"jsonBody": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Ivan Ivanov",
|
||||||
|
"grade": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Petr Petrov",
|
||||||
|
"grade": "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>ru.otus.projectwork</groupId>
|
||||||
|
<artifactId>contracts-tests</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>17</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<junit.version>5.10.2</junit.version>
|
||||||
|
<restassured.version>5.5.1</restassured.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.rest-assured</groupId>
|
||||||
|
<artifactId>rest-assured</artifactId>
|
||||||
|
<version>${restassured.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.rest-assured</groupId>
|
||||||
|
<artifactId>json-schema-validator</artifactId>
|
||||||
|
<version>${restassured.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.2.5</version>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<baseUrl>${baseUrl}</baseUrl>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package ru.otus.contracts;
|
||||||
|
|
||||||
|
import io.restassured.RestAssured;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
|
||||||
|
import static io.restassured.RestAssured.given;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||||
|
|
||||||
|
class UsersContractTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
RestAssured.baseURI = System.getProperty("baseUrl", "http://127.0.0.1:18080");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnUsersArrayByContract() {
|
||||||
|
given()
|
||||||
|
.when()
|
||||||
|
.get("/users/get/all")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body(matchesJsonSchemaInClasspath("schemas/users-all.schema.json"))
|
||||||
|
.body("size()", greaterThanOrEqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnUserByIdByContract() {
|
||||||
|
given()
|
||||||
|
.pathParam("id", 1)
|
||||||
|
.when()
|
||||||
|
.get("/users/{id}")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body(matchesJsonSchemaInClasspath("schemas/user-by-id.schema.json"))
|
||||||
|
.body("id", equalTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMockFailingMethod() {
|
||||||
|
given()
|
||||||
|
.pathParam("id", 500)
|
||||||
|
.when()
|
||||||
|
.get("/users/{id}")
|
||||||
|
.then()
|
||||||
|
.statusCode(500)
|
||||||
|
.body("error", equalTo("temporary_failure"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "name", "grade", "school_name", "city"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"grade": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"school_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "name", "grade"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"grade": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user