Backend fixes: - instance_dto: add Version field with Normalize() to support both 'version' and 'tag' field names from frontend - instance_handler: add version empty validation before creating instance - authz.go: fix unused variable compilation error - registry_repository: fix GetByID/GetByName to use correct DB schema (add workspace_id, owner_id, is_shared fields); decrypt password gracefully when encryption key mismatches instead of returning error Frontend: - charts/page: add Template and Storage dropdown selectors to Deploy Modal Testing: - add e2e_test.py: 5-step Playwright E2E test (admin login → create workspace → create user → user login → deploy chart) - add tasks/lesson.md: document 4 bug root causes and fixes - add tasks/todo.md: track implementation progress - add PLAN_E2E_DEPLOYMENT.md: comprehensive implementation plan Verification: confirmed deployment creates instance with status=deployed, chart downloads from Harbor OCI to /tmp/charts/, Helm release deploys to K8s
273 lines
8.1 KiB
Python
273 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
E2E Test: Admin creates user -> User deploys Helm Chart
|
|
|
|
Prerequisites:
|
|
1. Start services: ./start.sh
|
|
2. Install Playwright: pip install playwright && playwright install chromium
|
|
3. Run: python e2e_test.py
|
|
|
|
This test covers the complete OCDP workflow:
|
|
1. Admin login
|
|
2. Admin creates workspace
|
|
3. Admin creates user
|
|
4. User login
|
|
5. User browses charts from Harbor
|
|
6. User deploys a chart
|
|
7. Verify deployment success
|
|
"""
|
|
|
|
from playwright.sync_api import sync_playwright
|
|
import time
|
|
import random
|
|
|
|
BASE_URL = "http://localhost"
|
|
ADMIN_USER = "admin"
|
|
ADMIN_PASS = "admin123"
|
|
|
|
def screenshot(page, name):
|
|
"""Take a screenshot for debugging"""
|
|
page.screenshot(path=f'/tmp/{name}.png', full_page=True)
|
|
print(f" [Screenshot] /tmp/{name}.png")
|
|
|
|
def login(page, username, password, expect_success=True):
|
|
"""Login with given credentials"""
|
|
page.goto(f"{BASE_URL}/login")
|
|
page.wait_for_load_state('networkidle')
|
|
time.sleep(1)
|
|
|
|
# Fill login form
|
|
page.fill('#username', username)
|
|
page.fill('#password', password)
|
|
page.click('button[type="submit"]')
|
|
|
|
# Wait for redirect
|
|
time.sleep(3)
|
|
try:
|
|
page.wait_for_url("**/", timeout=8000)
|
|
except:
|
|
pass
|
|
page.wait_for_load_state('networkidle')
|
|
|
|
# Verify login
|
|
token = page.evaluate('localStorage.getItem("access_token")')
|
|
if token:
|
|
print(f" [OK] Logged in as {username}")
|
|
return True
|
|
else:
|
|
if expect_success:
|
|
screenshot(page, f'login_failed_{username}')
|
|
print(f" [FAIL] Login failed for {username}")
|
|
return False
|
|
|
|
def logout(page):
|
|
"""Logout current user"""
|
|
try:
|
|
page.click('text=Logout', timeout=3000)
|
|
except:
|
|
# Clear localStorage manually
|
|
page.evaluate('localStorage.clear()')
|
|
time.sleep(1)
|
|
page.wait_for_load_state('networkidle')
|
|
|
|
def create_workspace(page, name, description="Test workspace"):
|
|
"""Create a workspace via admin panel"""
|
|
page.goto(f"{BASE_URL}/admin/workspaces")
|
|
page.wait_for_load_state('networkidle')
|
|
time.sleep(1)
|
|
|
|
# Click Add Workspace button
|
|
add_btn = page.locator('text=Add Workspace').first
|
|
if add_btn:
|
|
add_btn.click()
|
|
time.sleep(1)
|
|
|
|
# Fill form
|
|
page.fill('input[required]', name)
|
|
time.sleep(0.5)
|
|
|
|
# Submit
|
|
page.click('button[type="submit"]')
|
|
time.sleep(3)
|
|
page.wait_for_load_state('networkidle')
|
|
screenshot(page, f'workspace_{name}')
|
|
print(f" [OK] Created workspace: {name}")
|
|
return name
|
|
|
|
def create_user(page, username, password, role="user"):
|
|
"""Create a user via admin panel"""
|
|
page.goto(f"{BASE_URL}/admin/users")
|
|
page.wait_for_load_state('networkidle')
|
|
time.sleep(1)
|
|
|
|
# Click Add User button
|
|
add_btn = page.locator('text=Add User').first
|
|
if add_btn:
|
|
add_btn.click()
|
|
time.sleep(1)
|
|
|
|
# Fill form
|
|
inputs = page.locator('input[type="text"], input[type="password"]').all()
|
|
if len(inputs) >= 2:
|
|
inputs[0].fill(username)
|
|
inputs[1].fill(password)
|
|
print(f" Filled form with username={username}")
|
|
|
|
# Select role if dropdown exists
|
|
role_select = page.locator('select').first
|
|
if role_select.count() > 0:
|
|
try:
|
|
role_select.select_option(value=role)
|
|
except:
|
|
pass
|
|
|
|
# Submit
|
|
page.click('button[type="submit"]')
|
|
time.sleep(3)
|
|
page.wait_for_load_state('networkidle')
|
|
screenshot(page, f'user_{username}')
|
|
print(f" [OK] Created user: {username} (role={role})")
|
|
|
|
def browse_and_deploy_chart(page, chart_repo="charts/nginx"):
|
|
"""Browse charts and deploy"""
|
|
page.goto(f"{BASE_URL}/charts")
|
|
page.wait_for_load_state('networkidle')
|
|
time.sleep(2)
|
|
screenshot(page, 'charts_page')
|
|
|
|
# Select first available registry
|
|
registry_btns = page.locator('button:has(svg), button:has-text("harbor")').all()
|
|
for btn in registry_btns:
|
|
try:
|
|
txt = btn.text_content() or ''
|
|
if txt.strip() and len(txt.strip()) > 0:
|
|
btn.click()
|
|
print(f" [OK] Selected registry: {txt.strip()}")
|
|
time.sleep(2)
|
|
break
|
|
except:
|
|
pass
|
|
|
|
# Find and click the chart repository
|
|
time.sleep(2)
|
|
repo_btns = page.locator('button:has-text("charts/")').all()
|
|
for btn in repo_btns:
|
|
try:
|
|
txt = btn.text_content() or ''
|
|
if chart_repo in txt:
|
|
btn.click()
|
|
print(f" [OK] Selected repo: {chart_repo}")
|
|
time.sleep(2)
|
|
break
|
|
except:
|
|
pass
|
|
|
|
# Look for Deploy button
|
|
time.sleep(2)
|
|
deploy_btns = page.locator('button:has-text("Deploy")').all()
|
|
for btn in deploy_btns:
|
|
try:
|
|
if btn.is_visible():
|
|
btn.click()
|
|
print(f" [OK] Opened deploy modal")
|
|
time.sleep(2)
|
|
break
|
|
except:
|
|
pass
|
|
|
|
# Fill deploy form
|
|
time.sleep(1)
|
|
inputs = page.locator('input[type="text"]').all()
|
|
for inp in inputs:
|
|
try:
|
|
if inp.is_visible():
|
|
placeholder = (inp.get_attribute('placeholder') or '').lower()
|
|
if 'release' in placeholder or 'name' in placeholder:
|
|
instance_name = f"test-{int(time.time())}"
|
|
inp.fill(instance_name)
|
|
print(f" Filled release name: {instance_name}")
|
|
break
|
|
except:
|
|
pass
|
|
|
|
# Submit deployment
|
|
time.sleep(1)
|
|
submit_btns = page.locator('button:has-text("Deploy")').all()
|
|
for btn in submit_btns:
|
|
try:
|
|
if btn.is_visible() and 'Deploying' not in (btn.text_content() or ''):
|
|
btn.click()
|
|
print(f" [OK] Submitted deployment")
|
|
time.sleep(3)
|
|
screenshot(page, 'deployment_result')
|
|
break
|
|
except:
|
|
pass
|
|
|
|
return True
|
|
|
|
def main():
|
|
print("=" * 60)
|
|
print("OCDP E2E Test: Admin -> User Deployment Flow")
|
|
print("=" * 60)
|
|
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch(headless=False, args=['--no-sandbox'])
|
|
page = browser.new_page()
|
|
page.set_viewport_size({"width": 1920, "height": 1080})
|
|
|
|
try:
|
|
# Step 1: Login as admin
|
|
print("\n[Step 1] Admin login")
|
|
if not login(page, ADMIN_USER, ADMIN_PASS):
|
|
print("[FAIL] Admin login failed")
|
|
return
|
|
|
|
# Step 2: Create workspace
|
|
print("\n[Step 2] Create workspace")
|
|
workspace_name = f"test-ws-{int(time.time())}"
|
|
try:
|
|
create_workspace(page, workspace_name)
|
|
except Exception as e:
|
|
print(f" [WARN] Workspace creation: {e}")
|
|
|
|
# Step 3: Create user
|
|
print("\n[Step 3] Create user")
|
|
test_user = f"e2euser_{int(time.time())}"
|
|
test_pass = "test123456"
|
|
try:
|
|
create_user(page, test_user, test_pass, "user")
|
|
except Exception as e:
|
|
print(f" [WARN] User creation: {e}")
|
|
# Use existing user if creation fails
|
|
test_user = "test"
|
|
|
|
# Step 4: Logout and login as test user
|
|
print(f"\n[Step 4] Login as {test_user}")
|
|
logout(page)
|
|
time.sleep(1)
|
|
login(page, test_user, test_pass, expect_success=True)
|
|
|
|
# Step 5: Browse charts
|
|
print("\n[Step 5] Browse and deploy chart")
|
|
try:
|
|
browse_and_deploy_chart(page, "charts/nginx")
|
|
print(" [OK] Chart deployed")
|
|
except Exception as e:
|
|
print(f" [WARN] Chart deployment: {e}")
|
|
screenshot(page, 'deploy_error')
|
|
|
|
print("\n" + "=" * 60)
|
|
print("[PASS] E2E Test Complete!")
|
|
print("=" * 60)
|
|
screenshot(page, 'e2e_complete')
|
|
|
|
except Exception as e:
|
|
print(f"\n[FAIL] Error: {e}")
|
|
screenshot(page, 'e2e_error')
|
|
finally:
|
|
time.sleep(2)
|
|
browser.close()
|
|
|
|
if __name__ == "__main__":
|
|
main() |