- Woodpecker workflows требуют агент с меткой isolation=dedicated-vm - шаги выполняются в lms-ci:1 с nested dockerd, без хост-Docker сокета - ci/Dockerfile: прогретый immutable-образ (Java, Python, Node, Gradle-кэш) - ci-dind.sh поднимает dockerd внутри privileged-шага; ci-build-image.sh собирает образ - deploy/WOODPECKER.md: отдельная VM, firewall, отсутствие production-секретов, trusted repo, чек-лист безопасности
174 lines
6.4 KiB
Bash
Executable File
174 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# LDD browser E2E: полный стек + Playwright (Chromium/Firefox/WebKit) на одноразовой БД.
|
||
set -euo pipefail
|
||
|
||
cd "$(dirname "$0")/.."
|
||
|
||
DB="lms_e2e_$(date -u +%H%M%S)_$$"
|
||
POSTGRES_WAS_RUNNING=0
|
||
STACK_STARTED=0
|
||
E2E_STATUS=1
|
||
|
||
log() { echo ">> $*"; }
|
||
|
||
playwright_version() {
|
||
(cd e2e && node -p "require('@playwright/test/package.json').version" 2>/dev/null || true)
|
||
}
|
||
|
||
host_playwright_usable() {
|
||
# WebKit на Fedora часто не запускается из-за Ubuntu-специфичных libicu/libjpeg;
|
||
# проверяем все три браузера, чтобы fallback сработал и при нехватке lib для Firefox.
|
||
(cd e2e && node -e '
|
||
const { chromium, firefox, webkit } = require("playwright");
|
||
(async () => {
|
||
for (const type of [chromium, firefox, webkit]) {
|
||
const browser = await type.launch();
|
||
await browser.close();
|
||
}
|
||
})().catch(() => process.exit(1));
|
||
') >/dev/null 2>&1
|
||
}
|
||
|
||
ensure_playwright_browsers() {
|
||
local browser detected missing=()
|
||
# Проверяем точные пути браузеров текущей версии Playwright, а не кэш целиком.
|
||
if ! detected=$(cd e2e && node -e '
|
||
const fs = require("fs");
|
||
const { chromium, firefox, webkit } = require("playwright");
|
||
for (const [name, type] of [["chromium", chromium], ["firefox", firefox], ["webkit", webkit]]) {
|
||
if (!fs.existsSync(type.executablePath())) console.log(name);
|
||
}
|
||
' 2>/dev/null); then
|
||
detected=$'chromium\nfirefox\nwebkit'
|
||
fi
|
||
while IFS= read -r browser; do
|
||
[[ -n "$browser" ]] && missing+=("$browser")
|
||
done <<<"$detected"
|
||
if ((${#missing[@]} == 0)); then
|
||
log "Браузеры Playwright уже установлены (Chromium/Firefox/WebKit)."
|
||
return
|
||
fi
|
||
log "Устанавливаю браузеры Playwright: ${missing[*]}; системные зависимости ставит CI."
|
||
(cd e2e && npx playwright install --no-progress "${missing[@]}")
|
||
}
|
||
|
||
cleanup() {
|
||
local code=$?
|
||
if ((STACK_STARTED)); then
|
||
log "Останавливаю тестовый стек..."
|
||
./run.sh stop >/dev/null 2>&1 || true
|
||
fi
|
||
docker exec -i lms-postgres psql -U lms -d postgres -v ON_ERROR_STOP=1 \
|
||
-c "DROP DATABASE IF EXISTS $DB;" >/dev/null 2>&1 || true
|
||
if ((POSTGRES_WAS_RUNNING == 0)); then
|
||
docker compose stop postgres >/dev/null 2>&1 || true
|
||
fi
|
||
exit $code
|
||
}
|
||
trap cleanup EXIT INT TERM
|
||
|
||
for port in 8080 8081 8082 8083 8090; do
|
||
if ss -ltn 2>/dev/null | grep -q ":$port "; then
|
||
echo "Порт $port занят. Остановите текущий стек (./run.sh stop) и повторите." >&2
|
||
exit 1
|
||
fi
|
||
done
|
||
|
||
command -v docker >/dev/null 2>&1 || { echo "Нужен docker" >&2; exit 1; }
|
||
command -v npx >/dev/null 2>&1 || { echo "Нужен Node.js/npx" >&2; exit 1; }
|
||
JAVA_BIN="${JAVA_BIN:-$(command -v java || true)}"
|
||
[[ -x "$JAVA_BIN" ]] || { echo "Не найден Java 21: $JAVA_BIN" >&2; exit 1; }
|
||
[[ -d e2e/node_modules ]] || { echo "Выполните: cd e2e && npm install" >&2; exit 1; }
|
||
|
||
if docker compose ps --status running --services 2>/dev/null | grep -qx postgres; then
|
||
POSTGRES_WAS_RUNNING=1
|
||
fi
|
||
|
||
log "Запускаю PostgreSQL..."
|
||
docker compose up -d postgres >/dev/null
|
||
for _ in $(seq 1 40); do
|
||
docker exec lms-postgres pg_isready -U lms -d postgres >/dev/null 2>&1 && break
|
||
sleep 1
|
||
done
|
||
docker exec lms-postgres pg_isready -U lms -d postgres >/dev/null
|
||
docker exec -i lms-postgres psql -U lms -d postgres -v ON_ERROR_STOP=1 \
|
||
-c "CREATE DATABASE $DB;" >/dev/null
|
||
|
||
log "Собираю bootJar..."
|
||
./gradlew bootJar --console=plain >/tmp/lms-ldd-e2e-bootjar.log
|
||
|
||
log "Запускаю стек..."
|
||
export LMS_DB_URL="jdbc:postgresql://localhost:5432/$DB"
|
||
export LMS_DB_NAME="$DB"
|
||
export LMS_DB_USER="lms"
|
||
export LMS_DB_PASSWORD="lms"
|
||
export LMS_INTERNAL_TOKEN="ldd-internal-token"
|
||
export LMS_SESSION_COOKIE_SECURE="false"
|
||
export JAVA_BIN
|
||
STACK_STARTED=1
|
||
./run.sh start
|
||
|
||
mkdir -p build/evidence
|
||
ensure_playwright_browsers
|
||
set +e
|
||
if host_playwright_usable; then
|
||
log "Запускаю Playwright в текущем окружении (Chromium/Firefox/WebKit)..."
|
||
(
|
||
cd e2e
|
||
LDD_WEB_URL="http://localhost:8090" \
|
||
LDD_API_URL="http://localhost:8080" \
|
||
npx playwright test --config=playwright.config.ts
|
||
)
|
||
E2E_STATUS=$?
|
||
else
|
||
version=$(playwright_version)
|
||
if [[ -z "$version" ]]; then
|
||
echo "WebKit не запускается в текущем окружении, а версию Playwright определить не удалось." >&2
|
||
E2E_STATUS=1
|
||
else
|
||
log "WebKit не запускается в текущем окружении; запускаю Chromium/Firefox/WebKit в Docker-образе Playwright ${version}."
|
||
docker run --rm --network host \
|
||
-v "$PWD:/work" \
|
||
-w /work/e2e \
|
||
-e LDD_WEB_URL="http://localhost:8090" \
|
||
-e LDD_API_URL="http://localhost:8080" \
|
||
"mcr.microsoft.com/playwright:v${version}-noble" \
|
||
npx playwright test --config=playwright.config.ts
|
||
E2E_STATUS=$?
|
||
fi
|
||
fi
|
||
set -e
|
||
|
||
python3 - "$E2E_STATUS" <<'PY'
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
status_code = int(sys.argv[1])
|
||
playwright_path = "build/evidence/playwright.json"
|
||
detail = "Playwright exit=" + str(status_code)
|
||
if os.path.isfile(playwright_path):
|
||
try:
|
||
root = json.load(open(playwright_path, encoding="utf-8"))
|
||
stats = root.get("stats", {})
|
||
detail += (
|
||
f"; expected={stats.get('expected', 0)}"
|
||
f", unexpected={stats.get('unexpected', 0)}"
|
||
f", flaky={stats.get('flaky', 0)}"
|
||
f", skipped={stats.get('skipped', 0)}"
|
||
)
|
||
except Exception as exc:
|
||
detail += "; не удалось разобрать playwright.json: " + str(exc)
|
||
status = "passed" if status_code == 0 else "failed"
|
||
with open("build/evidence/browser.json", "w", encoding="utf-8") as fh:
|
||
now = __import__("datetime").datetime.now(__import__("datetime").UTC).isoformat()
|
||
json.dump(
|
||
{"generatedAt": now, "checks": [{"id": "browser.e2e", "status": status, "detail": detail}]},
|
||
fh,
|
||
ensure_ascii=False,
|
||
indent=2,
|
||
)
|
||
print(("PASS " if status == "passed" else "FAIL ") + "browser.e2e: " + detail)
|
||
sys.exit(0 if status == "passed" else 1)
|
||
PY
|