119 lines
4.6 KiB
Python
119 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
tests/ui-sso.py — E2E браузерный тест SSO через Playwright.
|
|
|
|
Проверяет полный поток:
|
|
1. Редирект на Keycloak
|
|
2. Вход с логином/паролем
|
|
3. Возврат в приложение
|
|
4. CRUD через UI
|
|
5. Logout
|
|
|
|
Запуск:
|
|
pip install playwright && playwright install chromium
|
|
KC_LOGIN=tazet@narod.ru KC_PASS=*** python tests/ui-sso.py
|
|
|
|
Если Keycloak запрашивает OTP/MFA код — тест запросит его через stdin.
|
|
"""
|
|
|
|
import os, sys, time
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
APP_URL = os.environ.get("APP_URL", "https://whitelist.nodejsk8s.services.ngcloud.ru")
|
|
KC_LOGIN = os.environ.get("KC_LOGIN")
|
|
KC_PASS = os.environ.get("KC_PASS")
|
|
|
|
if not KC_LOGIN or not KC_PASS:
|
|
print("❌ Задай KC_LOGIN и KC_PASS")
|
|
print(" KC_LOGIN=tazet@narod.ru KC_PASS=*** python tests/ui-sso.py")
|
|
sys.exit(1)
|
|
|
|
passed = 0
|
|
failed = 0
|
|
|
|
def ok(name, condition, details=""):
|
|
global passed, failed
|
|
if condition:
|
|
passed += 1
|
|
print(f" ✅ {name}")
|
|
else:
|
|
failed += 1
|
|
print(f" ❌ {name} — {details}")
|
|
|
|
def run():
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch(headless=False)
|
|
page = browser.new_page()
|
|
|
|
print(f"\n🧪 SSO E2E браузерный тест")
|
|
print(f" App: {APP_URL}")
|
|
print(f" User: {KC_LOGIN}\n")
|
|
|
|
# ── 1. Заходим на / → редирект на Keycloak ──
|
|
page.goto(f"{APP_URL}/")
|
|
page.wait_for_timeout(2000)
|
|
ok("1. Редирект на Keycloak", "keycloak.nubes.ru" in page.url)
|
|
|
|
# ── 2. Логин в Keycloak ──
|
|
if page.locator("input[name=username], #username").count() > 0:
|
|
page.fill("input[name=username], #username", KC_LOGIN)
|
|
page.fill("input[name=password], #password", KC_PASS)
|
|
page.click("button[type=submit], input[type=submit], button[name=login]")
|
|
page.wait_for_timeout(3000)
|
|
|
|
# Проверяем OTP/MFA
|
|
if page.locator("input[name=otp], input[name=totp]").count() > 0:
|
|
otp = input("🔐 Введи код OTP/MFA: ").strip()
|
|
page.fill("input[name=otp], input[name=totp]", otp)
|
|
page.click("button[type=submit], input[type=submit]")
|
|
page.wait_for_timeout(3000)
|
|
|
|
ok("2. Вход в Keycloak", APP_URL in page.url or "ipwhitelist" in page.url.lower() or "WhiteList" in page.title())
|
|
else:
|
|
ok("2. Вход в Keycloak", False, "no login form")
|
|
|
|
# ── 3. Главная страница ──
|
|
page.wait_for_timeout(2000)
|
|
ok("3. Главная страница загружена", "Белые списки" in page.content() or "WhiteList" in page.title() or page.url.startswith(APP_URL))
|
|
|
|
# ── 4. Добавляем запись ──
|
|
ts = int(time.time())
|
|
cidr = f"10.{ts % 200}.{ts % 100}.{ts % 50}"
|
|
page.fill("input[name=value]", cidr)
|
|
page.fill("textarea[name=comment], input[name=comment]", f"test-{ts}")
|
|
page.click("button[type=submit]:has-text('Добавить'), button:has-text('Добавить')")
|
|
page.wait_for_timeout(2000)
|
|
ok("4. Запись добавлена", cidr in page.content())
|
|
|
|
# ── 5. Удаляем запись ──
|
|
delete_btn = page.locator(f"tr:has-text('{cidr}') button:has-text('Удалить'), tr:has-text('{cidr}') a:has-text('Удалить')").first
|
|
if delete_btn.count() > 0:
|
|
delete_btn.click()
|
|
page.wait_for_timeout(1000)
|
|
page.click("button:has-text('OK'), button:has-text('Да'), button:has-text('Удалить')")
|
|
page.wait_for_timeout(2000)
|
|
ok("5. Запись удалена", cidr not in page.content())
|
|
else:
|
|
ok("5. Запись удалена", False, "delete button not found")
|
|
|
|
# ── 6. Export ──
|
|
page.goto(f"{APP_URL}/export")
|
|
page.wait_for_timeout(1000)
|
|
ok("6. Export доступен", "text/plain" in page.content() or page.url.endswith("/export"))
|
|
|
|
# ── 7. Logout ──
|
|
page.goto(f"{APP_URL}/logout")
|
|
page.wait_for_timeout(3000)
|
|
ok("7. Logout → /login", "/login" in page.url or "keycloak" in page.url.lower())
|
|
|
|
# ── Итог ──
|
|
print(f"\n═══ ИТОГ ═══")
|
|
print(f" ✅ {passed} ❌ {failed} из {passed+failed}")
|
|
|
|
browser.close()
|
|
return failed == 0
|
|
|
|
if __name__ == "__main__":
|
|
ok = run()
|
|
sys.exit(0 if ok else 1)
|