hw4: finalize selenoid and ansible workflow with citrus tests

This commit is contained in:
2026-02-27 01:38:06 +03:00
parent c06e9a89f1
commit 7ddea2e997
36 changed files with 1171 additions and 122 deletions

5
ansible/ansible.cfg Normal file
View File

@@ -0,0 +1,5 @@
[defaults]
inventory = inventory/hosts.ini
host_key_checking = False
retry_files_enabled = False
interpreter_python = auto_silent

View File

@@ -0,0 +1,37 @@
---
run_apt_upgrade: false
ansible_ssh_private_key_file: /home/spawn/.ssh/id_ed25519
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o ServerAliveInterval=10 -o ServerAliveCountMax=3"
selenoid_dir: /opt/selenoid
selenoid_compose_file: docker-compose.selenoid.yml
selenoid_limit: 2
selenoid_ui_port: 8080
selenoid_container_network: "selenoid_default"
selenoid_host_port: 4444
selenoid_image: "aerokube/selenoid:1.11.3"
selenoid_ui_image: "aerokube/selenoid-ui:1.10.11"
selenoid_chrome_versions:
- "128.0"
- "127.0"
selenoid_firefox_versions:
- "125.0"
- "124.0"
ggr_dir: /opt/ggr
ggr_compose_file: docker-compose.ggr.yml
ggr_gateway_port: 4445
ggr_ui_listen_port: 8888
ggr_ui_public_port: 8888
ggr_nginx_public_port: 80
ggr_quota_name: guest
ggr_image: "aerokube/ggr:1.7.2"
ggr_ui_image: "aerokube/ggr-ui:latest-release"
nginx_image: "nginx:1.28.2"
run_tests_via_ansible: false
test_runner_execute_mode: control
test_runner_project_dir: /opt/otus-autotests/homework_1
test_runner_control_project_dir: /mnt/c/Users/spawn/IdeaProjects/otus-autotests/homework_1
test_runner_ui_test_class: ru.kovbasa.tests.CourseSearchTest
test_runner_browser: chrome

View File

@@ -0,0 +1,3 @@
---
ansible_connection: local
ggr_selenoid_host_override: selenoid

View File

@@ -0,0 +1,4 @@
---
ansible_host: 10.10.2.112
ansible_user: ansible
ggr_selenoid_host_override: selenoid

View File

@@ -0,0 +1,4 @@
---
ansible_host: 10.10.2.127
ansible_user: ansible
ggr_selenoid_host_override: selenoid

View File

@@ -0,0 +1,9 @@
[selenoid_nodes]
localhost
vm1
vm2
[ggr_gateway]
localhost
vm1
vm2

View File

@@ -0,0 +1,20 @@
---
- name: Provision Selenoid Nodes
hosts: selenoid_nodes
become: true
tags:
- provision
roles:
- common
- docker
- selenoid
- name: Provision GGR Gateway
hosts: ggr_gateway
become: true
tags:
- provision
roles:
- common
- docker
- ggr_gateway

View File

@@ -0,0 +1,4 @@
---
- import_playbook: provision.yml
- import_playbook: verify.yml
- import_playbook: tests.yml

View File

@@ -0,0 +1,11 @@
---
- name: Run tests on provisioned host
hosts: ggr_gateway
become: true
tags:
- tests
tasks:
- name: Execute test runner role when enabled
ansible.builtin.include_role:
name: test_runner
when: run_tests_via_ansible | bool

View File

@@ -0,0 +1,8 @@
---
- name: Verify deployed grid endpoints
hosts: ggr_gateway
become: true
tags:
- verify
roles:
- verify

View File

@@ -0,0 +1,19 @@
---
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Upgrade apt packages (optional)
ansible.builtin.apt:
upgrade: dist
when: run_apt_upgrade | bool
- name: Install base packages
ansible.builtin.apt:
name:
- ca-certificates
- curl
- gnupg
- lsb-release
state: present

View File

@@ -0,0 +1,32 @@
---
- name: Check whether docker CLI is already available
ansible.builtin.command: docker --version
register: docker_cli_check
changed_when: false
failed_when: false
- name: Install Docker packages when docker is missing
ansible.builtin.apt:
name:
- docker.io
- docker-compose-v2
state: present
when: docker_cli_check.rc != 0
- name: Enable Docker service when installed by role and systemd is available
ansible.builtin.service:
name: docker
state: started
enabled: true
when:
- docker_cli_check.rc != 0
- ansible_service_mgr == "systemd"
- name: Add current ansible user to docker group
ansible.builtin.user:
name: "{{ ansible_user }}"
groups: docker
append: true
when:
- ansible_user is defined
- docker_cli_check.rc != 0

View File

@@ -0,0 +1,37 @@
- name: Create GGR directory structure
ansible.builtin.file:
path: "{{ ggr_dir }}/{{ item }}"
state: directory
mode: "0755"
loop:
- quota
- nginx
- name: Render GGR quota file
ansible.builtin.template:
src: quota.xml.j2
dest: "{{ ggr_dir }}/quota/{{ ggr_quota_name }}.xml"
mode: "0644"
- name: Render nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: "{{ ggr_dir }}/nginx/default.conf"
mode: "0644"
- name: Render docker compose for GGR gateway
ansible.builtin.template:
src: docker-compose.ggr.yml.j2
dest: "{{ ggr_dir }}/{{ ggr_compose_file }}"
mode: "0644"
- name: Stop previous GGR gateway stack and remove orphans
ansible.builtin.command:
cmd: docker compose -f {{ ggr_compose_file }} down --remove-orphans
chdir: "{{ ggr_dir }}"
changed_when: false
- name: Start GGR gateway stack
ansible.builtin.command:
cmd: docker compose -f {{ ggr_compose_file }} up -d --force-recreate
chdir: "{{ ggr_dir }}"

View File

@@ -0,0 +1,46 @@
services:
ggr:
image: "{{ ggr_image }}"
container_name: ggr
restart: unless-stopped
ports:
- "{{ ggr_gateway_port }}:4444"
volumes:
- "./quota:/etc/grid-router/quota:ro"
command:
- "-listen"
- ":4444"
- "-quotaDir"
- "/etc/grid-router/quota"
- "-guests-allowed"
ggr-ui:
image: "{{ ggr_ui_image }}"
container_name: ggr-ui
restart: unless-stopped
ports:
- "{{ ggr_ui_public_port }}:8888"
volumes:
- "./quota:/etc/grid-router/quota:ro"
command:
- "-listen"
- ":8888"
- "-quota-dir"
- "/etc/grid-router/quota"
nginx:
image: "{{ nginx_image }}"
container_name: grid-nginx
restart: unless-stopped
depends_on:
- ggr
- ggr-ui
ports:
- "{{ ggr_nginx_public_port }}:80"
volumes:
- "./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro"
networks:
default:
name: "{{ selenoid_container_network }}"
external: true

View File

@@ -0,0 +1,53 @@
server {
listen 80;
server_name _;
location /ws/ {
proxy_pass http://selenoid:4444/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /vnc/ {
proxy_pass http://selenoid:4444/vnc/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /wd/hub/ {
proxy_pass http://ggr:4444/wd/hub/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ggr/ {
proxy_pass http://ggr-ui:8888/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://selenoid-ui:8080/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<qa:browsers xmlns:qa="urn:config.gridrouter.qatools.ru">
<browser name="chrome" defaultVersion="{{ selenoid_chrome_versions | first }}">
{% for version in selenoid_chrome_versions %}
<version number="{{ version }}">
<region name="default">
<host name="{{ ggr_selenoid_host_override | default(ansible_host | default(inventory_hostname)) }}" port="{{ selenoid_host_port }}" count="{{ selenoid_limit }}"/>
</region>
</version>
{% endfor %}
</browser>
<browser name="firefox" defaultVersion="{{ selenoid_firefox_versions | first }}">
{% for version in selenoid_firefox_versions %}
<version number="{{ version }}">
<region name="default">
<host name="{{ ggr_selenoid_host_override | default(ansible_host | default(inventory_hostname)) }}" port="{{ selenoid_host_port }}" count="{{ selenoid_limit }}"/>
</region>
</version>
{% endfor %}
</browser>
</qa:browsers>

View File

@@ -0,0 +1,68 @@
---
- name: Create Selenoid directory
ansible.builtin.file:
path: "{{ selenoid_dir }}"
state: directory
mode: "0755"
- name: Create logs directory
ansible.builtin.file:
path: "{{ selenoid_dir }}/{{ item }}"
state: directory
mode: "0777"
loop:
- logs
- name: Render browsers.json
ansible.builtin.template:
src: browsers.json.j2
dest: "{{ selenoid_dir }}/browsers.json"
mode: "0644"
- name: Render docker compose for Selenoid
ansible.builtin.template:
src: docker-compose.selenoid.yml.j2
dest: "{{ selenoid_dir }}/{{ selenoid_compose_file }}"
mode: "0644"
- name: Ensure default chrome version is in the configured versions list
ansible.builtin.assert:
that:
- (selenoid_chrome_versions | length) >= 2
- (selenoid_firefox_versions | length) >= 2
fail_msg: "Invalid Selenoid config: provide at least 2 chrome and 2 firefox versions"
- name: Check shared Docker network for grid stack
ansible.builtin.command:
cmd: docker network inspect {{ selenoid_container_network }}
register: shared_grid_network
changed_when: false
failed_when: false
- name: Create shared Docker network for grid stack
ansible.builtin.command:
cmd: docker network create {{ selenoid_container_network }}
when: shared_grid_network.rc != 0
- name: Stop previous Selenoid stack and remove orphans
ansible.builtin.command:
cmd: docker compose -f {{ selenoid_compose_file }} down --remove-orphans
chdir: "{{ selenoid_dir }}"
changed_when: false
- name: Pull required Chrome images for Selenoid
ansible.builtin.command:
cmd: "docker pull selenoid/vnc:chrome_{{ item }}"
loop: "{{ selenoid_chrome_versions }}"
changed_when: false
- name: Pull required Firefox images for Selenoid
ansible.builtin.command:
cmd: "docker pull selenoid/vnc:firefox_{{ item }}"
loop: "{{ selenoid_firefox_versions }}"
changed_when: false
- name: Start Selenoid stack
ansible.builtin.command:
cmd: docker compose -f {{ selenoid_compose_file }} up -d
chdir: "{{ selenoid_dir }}"

View File

@@ -0,0 +1,26 @@
{
"chrome": {
"default": "{{ selenoid_chrome_versions | first }}",
"versions": {
{% for version in selenoid_chrome_versions %}
"{{ version }}": {
"image": "selenoid/vnc:chrome_{{ version }}",
"port": "4444",
"path": "/"
}{% if not loop.last %},{% endif %}
{% endfor %}
}
},
"firefox": {
"default": "{{ selenoid_firefox_versions | first }}",
"versions": {
{% for version in selenoid_firefox_versions %}
"{{ version }}": {
"image": "selenoid/vnc:firefox_{{ version }}",
"port": "4444",
"path": "/wd/hub"
}{% if not loop.last %},{% endif %}
{% endfor %}
}
}
}

View File

@@ -0,0 +1,46 @@
services:
selenoid:
image: "{{ selenoid_image }}"
container_name: selenoid
restart: unless-stopped
environment:
- DOCKER_API_VERSION=1.45
ports:
- "{{ selenoid_host_port }}:4444"
volumes:
- "./browsers.json:/etc/selenoid/browsers.json:ro"
- "/var/run/docker.sock:/var/run/docker.sock"
- "./logs:/opt/selenoid/logs"
command:
- "-limit"
- "{{ selenoid_limit }}"
- "-conf"
- "/etc/selenoid/browsers.json"
- "-log-output-dir"
- "/opt/selenoid/logs"
- "-container-network"
- "{{ selenoid_container_network }}"
- "-timeout"
- "3m"
- "-session-attempt-timeout"
- "2m"
- "-service-startup-timeout"
- "2m"
selenoid-ui:
image: "{{ selenoid_ui_image }}"
container_name: selenoid-ui
restart: unless-stopped
depends_on:
- selenoid
ports:
- "{{ selenoid_ui_port }}:8080"
command:
- "--selenoid-uri"
- "http://selenoid:4444"
networks:
default:
name: "{{ selenoid_container_network }}"
external: true

View File

@@ -0,0 +1,38 @@
---
- name: Install Java and Maven for test execution
ansible.builtin.apt:
name:
- openjdk-21-jdk
- maven
state: present
update_cache: true
when: test_runner_execute_mode == "target"
- name: Run UI smoke test via Selenoid endpoint
ansible.builtin.command:
cmd: >
mvn
"-Dexecution.mode=selenoid"
"-Dbrowser={{ test_runner_browser }}"
"-Dbrowser.version={{ selenoid_chrome_versions[0] }}"
"-Dselenoid.url=http://{{ (ansible_host | default('127.0.0.1')) if test_runner_execute_mode == 'control' else '127.0.0.1' }}/wd/hub"
"-Dtest={{ test_runner_ui_test_class }}"
test
chdir: "{{ test_runner_control_project_dir if test_runner_execute_mode == 'control' else test_runner_project_dir }}"
environment:
JAVA_HOME: /usr/lib/jvm/java-21-openjdk-amd64
delegate_to: "{{ 'localhost' if test_runner_execute_mode == 'control' else omit }}"
become: false
register: ui_test_result
changed_when: false
- name: Run Citrus API tests
ansible.builtin.command:
cmd: mvn -f citrus-tests/pom.xml test
chdir: "{{ test_runner_control_project_dir if test_runner_execute_mode == 'control' else test_runner_project_dir }}"
environment:
JAVA_HOME: /usr/lib/jvm/java-21-openjdk-amd64
delegate_to: "{{ 'localhost' if test_runner_execute_mode == 'control' else omit }}"
become: false
register: citrus_test_result
changed_when: false

View File

@@ -0,0 +1,40 @@
---
- name: Wait for Selenoid status endpoint
ansible.builtin.uri:
url: "http://127.0.0.1:{{ selenoid_host_port }}/status"
method: GET
status_code: 200
register: selenoid_status_response
retries: 20
delay: 3
until: selenoid_status_response.status == 200
- name: Wait for GGR ping endpoint
ansible.builtin.uri:
url: "http://127.0.0.1:{{ ggr_gateway_port }}/ping"
method: GET
status_code: 200
register: ggr_ping_response
retries: 20
delay: 3
until: ggr_ping_response.status == 200
- name: Wait for gateway UI endpoint
ansible.builtin.uri:
url: "http://127.0.0.1:{{ ggr_nginx_public_port }}/"
method: GET
status_code: 200
register: ggr_ui_response
retries: 20
delay: 3
until: ggr_ui_response.status == 200
- name: Wait for gateway webdriver status endpoint
ansible.builtin.uri:
url: "http://127.0.0.1:{{ ggr_nginx_public_port }}/wd/hub/status"
method: GET
status_code: 200
register: wd_status_response
retries: 20
delay: 3
until: wd_status_response.status == 200