Тестовый проект для Semaphore UI, построенный на основе Bookwright v1.4.0 (b30d7e6).
Текущая release-матрица Semaphore v2.19.12 полностью подтверждена в Linux CI: 11 профилей
конфигураций прошли 2026-09-04. Переход v2.19.8 → v2.19.12 отдельно прошёл на SQLite и
PostgreSQL.
- Java 21;
- Gradle;
- JUnit 5;
- Retrofit и OkHttp;
- Playwright;
- Guice;
- AssertJ;
- Allure;
- Awaitility.
Framework адаптирован под Semaphore с сохранением архитектуры Bookwright v1.4.0: API и steps разделены как target/domain, сценарные данные принадлежат typed fixtures, а состояние preconditions читается только через typed TestStore.
test-environment/profile up core-sqlite-localSemaphore будет доступен на http://localhost:3000.
test-environment/profile test core-sqlite-localКоманда сама проверяет readiness и добавляет точную конфигурацию стенда в Allure environment. Остановка с сохранением SQLite volume: test-environment/profile down core-sqlite-local. Полное удаление состояния требует явной команды test-environment/profile clean core-sqlite-local --yes.
Core-набор также защищает lifecycle удаления проекта: после остановки task проект и зависимые
ресурсы удаляются корректно. Known-defect canary для v2.19.8 показывает, что удаление во время
running ошибочно возвращает 204, оставляет executor работающим и заканчивается FK errors.
Воспроизведение и source boundary описаны в
test-environment/project-deletion-running-task-defect.md.
Static inventory проверяется в обоих штатных форматах — INI static и YAML static-yaml.
Для каждого формата сценарий сохраняет две группы с разными host aliases и выполняет template с
default limit. Task output подтверждает выполнение только выбранной группы.
Password-login security test сравнивает ответы для существующего и неизвестного аккаунта, проверяет
отсутствие session cookie и корректный отказ пустого пароля. На v2.19.8 пять последовательных
ошибок остаются без throttle и audit warning; этот security gap описан в
test-environment/password-login-brute-force-protection-gap.md.
Тот же core-профиль выполняет plan-only сценарии на встроенных в release image Terraform 1.11.3 и
OpenTofu 1.11.0. Минимальный локальный module не скачивает providers; workspace inventories типов
terraform-workspace и tofu-workspace подтверждаются реальным output каждого tool. Привязанный
Variable Group передаёт секрет типа env с префиксом TF_VAR_: module сравнивает его SHA-256 и
выводит только безопасный marker, а тест исключает plaintext из API, structured/raw output и Allure.
Короткий browser smoke на том же стенде проверяет password login, запуск подготовленного executable template через UI и обязательность project name до отправки запроса:
./gradlew uiTest -DSTAND=semaphore -DSEMAPHORE_PROFILE=core-sqlite-localТот же набор выполняется без копирования тестов на PostgreSQL, MySQL и MariaDB. Профили используют общий порт и запускаются последовательно:
test-environment/profile down core-sqlite-local
test-environment/profile up core-postgres-local
test-environment/profile test core-postgres-local
test-environment/profile down core-postgres-local
test-environment/profile up core-mysql-local
test-environment/profile test core-mysql-local
test-environment/profile down core-mysql-local
test-environment/profile up core-mariadb-local
test-environment/profile test core-mariadb-localProduction-like вариант выполняет задачи в отдельном persistent runner:
test-environment/profile down core-postgres-local
test-environment/profile up prod-postgres-runner
test-environment/profile test prod-postgres-runnerRunner регистрируется автоматически, сохраняет долгоживущий token в отдельном volume и через admin API назначается default runner. API-тесты подтверждают active, registered, is_default, online, heartbeat, exact tag routing по persisted used_runner_id и capacity 1: второй task остаётся в waiting, пока первый занимает runner.
При отсутствии подходящего активного runner поведение отличается от capacity: v2.19.8 переводит task в error: no runners available, а не сохраняет в очереди. Это воспроизведено для временно отключённого matching runner и несуществующего tag; подробности находятся в test-environment/runner-unavailable-routing-defect.md.
Ещё одно отличие remote execution: v2.19.8 теряет secret survey variables перед dispatch, поэтому задача получает undefined variable. Профиль содержит безопасный known-defect canary без вывода значения; исправление upstream #4086 уже входит в v2.20.0-alpha1. Доказательства и критерий переключения на positive regression описаны в test-environment/remote-runner-survey-secrets-defect.md.
SSH feature-профиль проверяет зашифрованный access key сразу на двух клиентских границах: Git clone по SSH и подключение Ansible к удалённому target:
test-environment/profile down prod-postgres-runner
test-environment/profile up feature-ssh-local
test-environment/profile test feature-ssh-localДва изолированных SSH-сервера доступны только внутри Compose network и принимают разные сгенерированные ключи. Положительный сценарий подтверждает удалённое выполнение playbook, отрицательный — полезную clone-диагностику с неверным ключом. Rotation-сценарий сначала получает отказ второго сервера со старым ключом, обновляет secret у того же access key через API и затем подтверждает успешные Git clone и Ansible SSH. Все сценарии проверяют отсутствие private key и passphrase в API и task output.
Приватный Git по HTTPS проверяется отдельно, через локальный NGINX с self-signed TLS и обязательной Basic Auth:
test-environment/profile down feature-ssh-local
test-environment/profile up feature-git-https
test-environment/profile test feature-git-httpsПрофиль передаёт доверенный CA в Git-процессы через штатный SEMAPHORE_FORWARDED_ENV_VARS. Positive-сценарий выполняет playbook после authenticated clone, negative подтверждает отказ без access key. Password не попадает в API/Allure diagnostics, а login и password отсутствуют в structured/raw task output.
OIDC feature-профиль выполняет полный браузерный вход через локальный Dex и проверяет callback, session, return path, provisioning external user, повторный вход, logout, конфликт с локальным email и отказ недоступного provider:
test-environment/profile down feature-ssh-local
test-environment/profile up feature-oidc-local
test-environment/profile test feature-oidc-localProduction-like OIDC-вариант повторяет тот же контракт через pinned NGINX, HTTPS и non-root public URL /semaphore на PostgreSQL:
test-environment/profile down feature-oidc-local
test-environment/profile up feature-proxy-oidc
test-environment/profile test feature-proxy-oidcTLS certificate и JVM truststore генерируются локально в игнорируемом build/test-fixtures. Тест дополнительно проверяет Secure, HttpOnly и path session cookie, routing API/assets через subpath и возврат OIDC callback на public HTTPS origin.
Ротация database encryption keyring проверяется отдельным трёхфазным lifecycle на PostgreSQL:
test-environment/profile encryption-rotation-test feature-encryption-rotationСценарий переключает primary без рестарта, подтверждает одновременное чтение старого и запись нового ciphertext, выполняет vault check/vault rekey, удаляет retired key и повторно выполняет сохранённый template. Test-only key material генерируется в игнорируемом build/test-fixtures/encryption-rotation.
LDAPS feature-профиль поднимает pinned OpenLDAP, выполняет service search и user bind по TLS, а затем проверяет provisioning/reuse external user, logout, неверный пароль и защиту локального account:
test-environment/profile down feature-oidc-local
test-environment/profile up feature-ldap-tls
test-environment/profile test feature-ldap-tlsTOTP MFA проверяется отдельным self-contained профилем:
test-environment/profile down feature-ldap-tls
test-environment/profile up feature-totp-local
test-environment/profile test feature-totp-localОбщий totpTest покрывает API self-enrollment, TOTP_REQUIRED, неверный и корректный RFC 6238 passcode, recovery, повторный enrollment и отказ уже использованного recovery code. Browser-сценарий отдельно проверяет Security settings, QR/recovery-code rendering, challenge и recovery form. OTP material редактируется в HTTP и raw Allure JSON, а чувствительные browser artifacts при падении не публикуются.
Dynamic runner profile проверяет start/finish webhook, запуск отдельного one-off runner и реальное выполнение задачи:
test-environment/profile down feature-ldap-tls
test-environment/profile up feature-dynamic-runner
test-environment/profile test feature-dynamic-runnerНа v2.19.8 задача завершается успешно, но one-off runner не выходит после terminal progress. Профиль оставлен ручным красным reproducer и не входит в стабильную CI matrix. Анализ и вероятная причина находятся в test-environment/dynamic-runner-one-off-exit-defect.md.
Короткие Bash-команды и background child проверяются отдельным shell-output профилем:
test-environment/profile down feature-dynamic-runner
test-environment/profile up feature-shell-output
test-environment/profile test feature-shell-outputНа v2.19.12 task получает success, но сохранённый output может потерять целиком stdout или
stderr. Строгий тест обоих потоков оставлен ручным красным reproducer и не входит в стабильный
PR/nightly gate. Причина, CI-доказательства и два upstream-исправления описаны в
test-environment/shell-output-loss-defect.md.
Экспериментальный schedule-профиль воспроизводит реальное cron/run_at исполнение в non-UTC timezone:
test-environment/profile down feature-ssh-local
test-environment/profile up feature-schedule-timezone
test-environment/profile test feature-schedule-timezoneНа v2.19.8 оба сценария локально воспроизводят дефект: активное расписание сохраняется, но task не создаётся. Профиль пока не включён в CI matrix; доказательства и ожидаемое поведение находятся в test-environment/schedule-execution-defect.md.
Проверка обновления опубликованных образов на сохранённой SQLite или PostgreSQL запускается отдельной командой:
test-environment/profile upgrade-test upgrade-sqlite-local
test-environment/profile down upgrade-sqlite-local
test-environment/profile upgrade-test upgrade-postgres-localТекущий upgrade-путь — v2.19.8 → v2.19.12. Он успешно подтвердил сохранность ресурсов,
access keys и task output на SQLite и PostgreSQL в Linux CI 2026-09-04. Предыдущая пара
v2.19.7 → v2.19.8 прошла на обеих СУБД 2026-08-19. Upgrade остаётся отдельным наблюдаемым
gate: он проверяет миграцию сохранённого состояния, а не только чистую установку.
Диагностика зафиксирована в test-environment/v2.19.8-regression-report.md; исторический
schema-дефект пары v2.19.6 → v2.19.7 — в test-environment/upgrade-report.md.
GitHub Actions разделены по стоимости и назначению:
CIзапускается для каждого pull request и push вmain: сначала выполняет framework quality gate, затем core API suite и короткий Chromium UI smoke наcore-sqlite-local;Configuration matrixежедневно в01:30 UTCи вручную проверяет PostgreSQL, MySQL, MariaDB, production-like PostgreSQL с persistent runner, SSH, приватный HTTPS Git, прямой и HTTPS/subpath OIDC, LDAPS, TOTP и ротацию database encryption keyring;Release upgradeеженедельно по воскресеньям в03:30 UTCи вручную проверяет обновлениеv2.19.8 → v2.19.12на SQLite и PostgreSQL.
Matrix jobs используют отдельные GitHub-hosted runners и выполняются параллельно с fail-fast: false. JUnit, HTML-отчёты, Allure results и диагностика контейнеров при падении сохраняются как artifacts. Upgrade workflow не входит в PR gate; зелёный job должен означать и сохранность данных, и полную финализацию task output.
При ручном запуске Configuration matrix можно включить inputs include_schedule_investigation
и/или include_shell_output_investigation. Тогда к матрице только для этого run добавятся
соответствующие известные красные defect-профили, чтобы подтвердить проблему на Linux и собрать
стандартные артефакты; ежедневный запуск остаётся зелёным gate без expected failures.
После каждого CI, nightly matrix или release-upgrade запуска Allure автоматически собирается в готовый HTML-сайт и загружается как artifact allure-html-<run>-<attempt>. Каждый Allure-отчёт собирается в single-file mode: после скачивания достаточно распаковать архив и открыть index.html двойным кликом — локальный HTTP-сервер не нужен. Для matrix run стартовая страница содержит отдельный отчёт каждого профиля, поэтому результаты разных СУБД не смешиваются в retries.
Завершённые запуски доверенной ветки main дополнительно публикуются на GitHub Pages. Мини-сайт хранит не более 60 запусков за последние 30 дней, группирует их по датам и позволяет фильтровать по workflow и версии Semaphore. Для каждого запуска доступны итоговый статус, commit, профили, распределение тестов и отдельные Allure-отчёты. PR и external-environment прогоны намеренно не публикуются. Сгенерированная история хранится в ветке gh-pages; её не следует редактировать вручную. Перед первым deployment владелец репозитория должен один раз выбрать Settings → Pages → Source → GitHub Actions.
Тест проверяет health, неверный и корректный login, создаёт изолированный проект и основную цепочку ресурсов:
project → access key → local Git repository → inventory → task template
→ task execution → success status → output marker
→ inactive cron schedule → schedule verification
→ guest RBAC → assigned project access → forbidden mutation
→ unassigned project isolation
После теста Bookwright LIFO cleanup удаляет проектные данные в обратном порядке. Для RBAC используется один стабильный fixture-пользователь bookwright-rbac-guest: повторные запуски переиспользуют его, потому что Semaphore не позволяет удалить пользователя после создания login-сессии.
Отдельный RBAC-набор фиксирует встроенные контракты manager и task_runner. Manager может создавать проектные ресурсы и запускать задачи, но не может удалить проект или управлять участниками. Task runner может запускать задачи, но получает 403 при изменении ресурсов, проекта и состава участников.
API-token-набор создаёт ограниченный по времени token, проверяет prefix-only listing, аутентифицирует отдельный Retrofit session через Bearer header и создаёт проект. После отзыва тот же token получает 401; создание уже истёкшего token отклоняется с 400. Полное значение не попадает в URL, step parameters или HTTP/Allure attachments: creation response намеренно скрывается, Authorization редактируется, а delete использует публичный восьмисимвольный prefix.
User lifecycle-набор проверяет поддерживаемую Community API последовательность create → update → delete → absence → recreate на одноразовом typed fixture. У модели пользователя в текущем Semaphore нет поля active/disabled и endpoints deactivate/reactivate, поэтому такой контракт не имитируется подменой password/delete.
File inventory-набор создаёт repository-backed type=file, выполняет playbook через inventory-файл из доверенного Git fixture и проверяет сохранённый repository_id. Отдельный безопасный canary фиксирует дефект v2.19.8: create принимает traversal-путь ../…, хотя update корректно возвращает 400; такой inventory не запускается.
Отдельный security smoke создаёт login_password access key с уникальным маркером, использует его как inventory credential при выполнении задачи и проверяет отсутствие plaintext в create/get/list API, структурированном и raw task output, Allure и JUnit artifacts.
Variable Group-набор создаёт смешанную группу с JSON extra vars, ENV и секретами типов var/env, переименовывает сохранённый secret без замены значения и реально выполняет variables.yml. Playbook проверяет секреты по SHA-256 под no_log и выводит только безопасный marker; тест отдельно контролирует create/get/list API и structured/raw output. Тот же контракт прошёл на SQLite и PostgreSQL v2.19.8; пустое имя ENV-переменной получает диагностируемый 400.
Terraform/OpenTofu-набор отдельно проверяет нативный контракт TF_VAR_*: уникальный secret хранится
в Variable Group как env, подключается к обоим templates и действительно читается как Terraform
input variable. Provider-free module публикует marker только после совпадения хеша; plaintext не
появляется в Variable Group API, task output или HTTP/Allure diagnostics.
Build/Deploy-набор создаёт связанную пару Ansible templates и вручную выбирает успешную build-задачу
при запуске deploy. Semaphore назначает build start_version, передаёт её как
SEMAPHORE_TASK_TARGET_VERSION, сохраняет build_task_id у deploy и передаёт ту же версию как
SEMAPHORE_TASK_INCOMING_VERSION. Оба playbook сверяют API metadata с executor environment и выводят
безопасные version markers. В detail API deploy хранится связь, а отображаемая версия берётся из
вложенного build_task в task history — собственного поля version у deploy-задачи нет.
Survey/task override-набор сохраняет в template enum, integer, string, env-target и secret survey variables, затем запускает survey-overrides.yml с переопределёнными значениями, template/task arguments и Ansible limit/tags/skip_tags/diff/skip_galaxy_install. Task действительно выполняется на SQLite и PostgreSQL с local execution, secret проверяется по SHA-256 под no_log и отсутствует в structured/raw output. Persistent runner на v2.19.8 теряет survey secret перед dispatch; это покрыто отдельным canary до перехода на upstream #4086. Неподдерживаемый survey target получает 400. Также зафиксирован gap v2.19.8: enum default вне списка принимается backend-ом; исправление уже есть в upstream v2.20.0-alpha1.
Webhook integration-набор создаёт token-authenticated searchable integration, project alias, header matcher и extractors из JSON body/header. Запросы с неверным token или event не запускают task, а валидный webhook возвращает task identifiers, сохраняет связь через integration_id и реально передаёт extracted значения в Ansible playbook. Token хранится в login_password access key и редактируется в API/Allure diagnostics.
Project backup/restore-набор экспортирует конфигурацию с access keys, repository, inventory, template и schedule после реального task execution. Backup не содержит plaintext authentication secret и task history; восстановленный проект получает новые ID с корректно перелинкованными ресурсами, после чего его template снова успешно выполняется. Workflows и external Secret Storage management не имитируются на Community image: обе возможности отключены feature flags и требуют Pro test subscription для честного e2e.
Negative restore checks подтверждают запрет операции для non-admin и отклонение отсутствующей repository-ссылки. На v2.19.8 найден общий off-by-one дефект duplicate validation: документ с двумя одинаковыми именами repository принимается и создаёт оба ресурса; canary и source boundary описаны в test-environment/project-backup-restore-validation-defect.md.
Concurrency-набор создаёт template с allow_parallel_tasks=true, чтобы не смешивать project limit с template-lock. При max_parallel_tasks=1 первая задача доходит до marker, а вторая стабильно остаётся в waiting; после освобождения слота она запускается. После API-обновления проекта до лимита 2 две задачи одновременно достигают marker и обе корректно останавливаются.
Git-набор проверяет выполнение задачи из явно выбранной ветки, диагностируемый отказ для отсутствующей ветки и недоступного HTTPS remote. Для authenticated clone дополнительно проверяется, что login/password не попадают в structured и raw task output.
SSH-набор использует отдельный typed fixture и проверяет успешный Git clone по SSH, выполнение playbook на SSH target, безопасный отказ с неверным ключом и ротацию секрета без замены key ID. Две зашифрованные тестовые пары ключей генерируются в игнорируемом build/test-fixtures/ssh; private keys не входят ни в Git, ни в Docker build context. Строгая проверка known_hosts не тестируется на закреплённом v2.19.8, потому что соответствующая конфигурация присутствует только в более новом upstream develop.
Task lifecycle-набор запускает безопасный long-running playbook, дожидается marker фактического выполнения и проверяет обычный stop и force-stop. В обоих случаях задача переходит в stopped, а шаг после паузы не выполняется.
Ansible-код берётся только из доверенных test-environment/fixtures/ansible, упакованных Compose в локальный read-only Git volume с ветками main и bookwright-fixture-ref. Внешний код при API-запуске не исполняется.
Полный набор инфраструктурных self-tests Bookwright и продуктовых тестов Semaphore:
JAVA_HOME=/opt/homebrew/opt/openjdk@21 \
./gradlew spotlessCheck test -DSTAND=semaphoresemaphore-ui-testing-assessment-plan.md— общий план;semaphore-testing-component-map.md— карта компонентов;outputs/issues-assessment/— полный реестр issues;test-environment/api-map.md— карта API и приоритеты автоматизации;test-environment/legacy-qa-review.md— разбор старых UI-тестов и ручных сценариев;test-environment/configuration-testing-overview.md— матрица клиентских конфигураций и опорные профили;test-environment/smoke-report.md— результаты проверки стенда.test-environment/known-defects.md— сводка актуальных дефектов с приоритетами и шагами воспроизведения.test-environment/schedule-execution-defect.md— воспроизводимый дефект cron/run-at execution.test-environment/dynamic-runner-one-off-exit-defect.md— воспроизводимый дефект завершения one-off runner.test-environment/runner-unavailable-routing-defect.md— fail-fast вместо recoverable queue при отсутствии matching runner.test-environment/remote-runner-survey-secrets-defect.md— потеря secret survey variables при remote dispatch.test-environment/survey-default-validation-defect.md— отсутствие enum default validation вv2.19.8.
Исходный код Semaphore хранится локально в /semaphore/ и исключён из этого репозитория.
externalTest запускает только read-only сценарии с тегом external: health, password login,
system info и список доступных проектов. Он не создаёт проекты, пользователей, templates или tasks и
не зависит от локальных Git/SSH/runner fixtures.
Адрес и credentials задаются явно через environment, чтобы случайно не направить полный локальный набор на пользовательский стенд и не передавать пароль аргументом launcher-скрипта:
export API_BASE_URL=https://semaphore.example.test/api/
export API_USERNAME=qa-reader
export API_PASSWORD='set-from-secret-storage'
scripts/run-external-tests.shДополнительные Gradle arguments передаются после имени скрипта. Для self-signed TLS можно передать
существующие -Dbookwright.test.ssl.trustStore=... и
-Dbookwright.test.ssl.trustStorePassword=.... Обычный apiTest остаётся локальным полным набором и
никогда не вызывается launcher-ом внешнего стенда.