Updated CI/CD pipeline to run E2E tests on the ITSulu K8s cluster (itsulu-testing namespace) instead of using external Runboat service. .gitlab-ci.yml Changes: - Removed preview stage (no external Runboat dependency) - Removed e2e_tests dependency on runboat_preview - Updated e2e_tests job to run on K8s cluster: * Uses bitnami/kubectl image for K8s access * Configures kubectl with KUBE_CONFIG secret * Creates ephemeral E2E test job on K8s * initContainer: Creates test database from template * container: Installs Odoo, addon, Playwright, runs pytest * Waits for job completion and downloads report * Reports available in GitLab artifacts - Consolidated performance tests into unit_tests stage (same runner) K8s Job Manifest (inline YAML): - Namespace: itsulu-testing - Init container: postgres:15-alpine for DB setup - Test container: blog-publisher Docker image - Volumes: emptyDir for test results - TTL: 3600s (1 hour) for log retention - Uses existing secrets: test-db-info, gitlab-docker-creds e2e/conftest.py Changes: - Updated docstring: "Runs on ITSulu K8s cluster" - Changed RUNBOAT_TIMEOUT → K8S_TIMEOUT (300s) - Updated wait_for_odoo() to mention K8s namespace in error - Increased timeout: 180s → 300s (Odoo init + addon install) Required CI/CD Variables (now reduced): - KUBE_CONFIG: base64-encoded kubeconfig for itsulu-testing namespace * Only one variable needed (replaces 3 Runboat variables) * Must have permissions to create Jobs in itsulu-testing namespace Prerequisites (K8s cluster): - test-db-svc: PostgreSQL service in itsulu-testing namespace - odoo_template: Pre-seeded database - gitlab-docker-creds: Secret for image pull - test-runner: ServiceAccount with Job create permissions Benefits: - No external service dependency - Leverages existing ITSulu K8s infrastructure - Full isolation per test run (ephemeral jobs) - Persistent logs via TTL - Cost-effective (uses existing cluster) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
120 lines
3.5 KiB
Python
120 lines
3.5 KiB
Python
"""
|
|
Playwright E2E test configuration for ITSulu Blog Publisher.
|
|
Runs on ITSulu K8s cluster (itsulu-testing namespace).
|
|
Handles cold-start polling, authentication, and fixture setup.
|
|
"""
|
|
import os
|
|
import time
|
|
import requests
|
|
import pytest
|
|
|
|
BASE_URL = os.environ.get('ODOO_BASE_URL', 'http://localhost:8069')
|
|
K8S_TIMEOUT = 300 # seconds (Odoo startup + addon install can take time)
|
|
|
|
|
|
def wait_for_odoo(url, timeout=K8S_TIMEOUT):
|
|
"""
|
|
Poll Odoo login page until it responds (K8s container cold-start handling).
|
|
K8s pod startup + Odoo initialization + addon installation can take 60-120s.
|
|
"""
|
|
deadline = time.time() + timeout
|
|
last_error = None
|
|
|
|
while time.time() < deadline:
|
|
try:
|
|
response = requests.get(f"{url}/web/login", timeout=5)
|
|
if response.status_code == 200:
|
|
return
|
|
except Exception as e:
|
|
last_error = e
|
|
pass
|
|
time.sleep(2)
|
|
|
|
raise TimeoutError(
|
|
f"Odoo at {url} did not respond after {timeout}s. "
|
|
f"Running on ITSulu K8s cluster (itsulu-testing namespace). "
|
|
f"Last error: {last_error}"
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def browser_context_args(browser_context_args):
|
|
"""
|
|
Poll Runboat instance and set base URL for all tests.
|
|
Runs once per test session before any browser is created.
|
|
"""
|
|
print(f"\n📡 Waiting for Odoo at {BASE_URL}...")
|
|
wait_for_odoo(BASE_URL)
|
|
print("✅ Odoo is ready")
|
|
|
|
return {**browser_context_args, 'base_url': BASE_URL}
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def auth_state(browser, browser_context_args):
|
|
"""
|
|
Log in as admin and save authentication state.
|
|
Reused across all tests to avoid repeated login (30s+ per test).
|
|
"""
|
|
print("\n🔐 Authenticating as admin...")
|
|
ctx = browser.new_context(**browser_context_args)
|
|
page = ctx.new_page()
|
|
page.set_default_timeout(30_000)
|
|
|
|
try:
|
|
page.goto('/web/login')
|
|
page.get_by_label('Email').fill('admin')
|
|
page.get_by_label('Password').fill('admin')
|
|
page.get_by_role('button', name='Log in').click()
|
|
page.wait_for_url('**/odoo/**', timeout=30_000)
|
|
|
|
state = ctx.storage_state()
|
|
print("✅ Authentication successful")
|
|
ctx.close()
|
|
return state
|
|
except Exception as e:
|
|
ctx.close()
|
|
raise RuntimeError(f"Login failed: {e}")
|
|
|
|
|
|
@pytest.fixture
|
|
def page(browser, browser_context_args, auth_state):
|
|
"""
|
|
Authenticated Playwright page fixture.
|
|
Each test gets a fresh context with saved auth state.
|
|
"""
|
|
ctx = browser.new_context(**browser_context_args, storage_state=auth_state)
|
|
pg = ctx.new_page()
|
|
pg.set_default_timeout(30_000) # Odoo JS rendering is slow
|
|
|
|
yield pg
|
|
|
|
ctx.close()
|
|
|
|
|
|
@pytest.fixture
|
|
def admin_user(page):
|
|
"""
|
|
Helper to get admin user record (via API call within page context).
|
|
Useful for setting up test data that requires user relationships.
|
|
"""
|
|
# In a real Runboat instance, we'd fetch this via the web services
|
|
# For now, return a simple dict with admin info
|
|
return {
|
|
'id': 2, # Odoo default admin user ID
|
|
'name': 'Administrator',
|
|
'email': 'admin@example.com',
|
|
'login': 'admin',
|
|
}
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def blog_name():
|
|
"""Pre-configured test blog name (must exist in template DB)."""
|
|
return 'ITSulu Insights'
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def notification_email():
|
|
"""Email address for testing notifications."""
|
|
return 'test@itsulu.com'
|