mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
## Summary - Add comprehensive E2E test coverage for RUM sourcemap functionality (26 total tests) - API tests: 20 pytest tests (upload/list/delete/resolve operations + RBAC) - UI tests: 6 Playwright tests for error tracking UI - Helper scripts for RUM data ingestion ## Implementation Details ### API Tests (pytest) - **test_sourcemap_api.py**: 15 tests covering: - Sourcemap upload (multipart/form-data) - List sourcemaps - Delete sourcemaps - Stacktrace resolution with sourcemaps - **test_sourcemap_rbac.py**: 5 tests for role-based access control - **ingest_rum_errors.py**: Helper to inject synthetic RUM error data for UI tests - **ingest_rum_performance.py**: Helper to enable RUM feature in UI ### UI Tests (Playwright) - **sourcemap-ui.spec.js**: 6 tests validating: - Error tracking page loads - Query execution - Error list display - Error detail navigation - Metadata display - Stack trace display - **rumPage.js**: Updated page object with API response interception - Workaround for Vue 3 @click handlers not responding to Playwright clicks - Navigate via URL using error IDs extracted from API responses - Tests run in **parallel mode** with proper assertions (no graceful skips) ## Test Execution ```bash # API tests (pytest) cd tests/api-testing pytest tests/test_sourcemap_api.py -v # 15 tests pytest tests/test_sourcemap_rbac.py -v # 5 tests # UI tests (Playwright) cd tests/ui-testing npx playwright test playwright-tests/RUM/sourcemap-ui.spec.js --workers=3 # 6 tests ``` ## Technical Notes - Vue 3 production builds don't respond to Playwright's synthetic click events on @click handlers - Solution: Intercept API responses to extract error IDs, then navigate directly via URL - All tests use hard assertions - tests fail if data doesn't exist (no graceful skips) --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Shrinath Rao <undefined@users.noreply.github.com>
115 lines
3.8 KiB
Python
115 lines
3.8 KiB
Python
import requests
|
|
import pytest
|
|
import os
|
|
import random
|
|
import string
|
|
import logging
|
|
from pathlib import Path
|
|
import base64
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
BASE_URL = os.environ["ZO_BASE_URL"]
|
|
DEFAULT_ORG_ID = os.environ.get("TEST_ORG_ID", "default")
|
|
root_dir = Path(__file__).parent.parent.parent
|
|
|
|
@pytest.fixture(scope="module")
|
|
def random_string():
|
|
"""Generate a random string of specified length."""
|
|
def _generate(length=5):
|
|
# ascii_letters consist of alphabets for 'a' to 'z' and 'A' to 'Z'
|
|
# digits consist of '0' to '9'
|
|
characters = string.ascii_letters + string.digits
|
|
return "".join(random.choices(characters, k=length))
|
|
return _generate
|
|
|
|
def _create_session_inner():
|
|
s = requests.Session()
|
|
username = os.environ["ZO_ROOT_USER_EMAIL"]
|
|
password = os.environ["ZO_ROOT_USER_PASSWORD"]
|
|
s.auth = (username, password)
|
|
resp = s.post(BASE_URL)
|
|
return s
|
|
|
|
|
|
def _create_session_inner_v2():
|
|
s = requests.Session()
|
|
username = os.environ["ZO_ROOT_USER_EMAIL"]
|
|
password = os.environ["ZO_ROOT_USER_PASSWORD"]
|
|
# Set Authorization header for all requests
|
|
basic_auth = base64.b64encode(f"{username}:{password}".encode()).decode()
|
|
s.headers.update({"Authorization": f"Basic {basic_auth}"})
|
|
return s
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def create_session():
|
|
"""Create a session and return it."""
|
|
return _create_session_inner_v2()
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def base_url():
|
|
"""Return the base URL with trailing slash."""
|
|
url = BASE_URL
|
|
return url if url.endswith('/') else url + '/'
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def org_id():
|
|
"""Return the organization ID for testing.
|
|
|
|
Can be overridden via TEST_ORG_ID environment variable.
|
|
"""
|
|
return DEFAULT_ORG_ID
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def ingest_data():
|
|
"""Ingest data into the openobserve running instance."""
|
|
import time
|
|
|
|
# Use v2 session for consistent auth header
|
|
session = _create_session_inner_v2()
|
|
|
|
# Ingest main test data
|
|
with open(root_dir / "test-data/logs_data.json") as f:
|
|
data = f.read()
|
|
|
|
stream_name = "stream_pytest_data"
|
|
org = DEFAULT_ORG_ID
|
|
base_url_with_slash = BASE_URL if BASE_URL.endswith('/') else BASE_URL + '/'
|
|
url = f"{base_url_with_slash}api/{org}/{stream_name}/_json"
|
|
resp1 = session.post(url, data=data, headers={"Content-Type": "application/json"})
|
|
logging.info("Main data ingested successfully, status code: %s", resp1.status_code)
|
|
assert resp1.status_code == 200, \
|
|
f"Failed to ingest main test data: {resp1.status_code} - {resp1.text[:500]}"
|
|
|
|
# Ingest camel case test data
|
|
with open(root_dir / "test-data/match_all.json") as f:
|
|
camel_data = f.read()
|
|
|
|
resp2 = session.post(url, data=camel_data, headers={"Content-Type": "application/json"})
|
|
logging.info("Camel case data ingested successfully, status code: %s", resp2.status_code)
|
|
assert resp2.status_code == 200, \
|
|
f"Failed to ingest camel case test data: {resp2.status_code} - {resp2.text[:500]}"
|
|
|
|
# Flush data to ensure it's indexed
|
|
flush_url = f"{base_url_with_slash}node/flush"
|
|
logging.info("Flushing data to ensure indexing...")
|
|
flush_resp = session.put(flush_url)
|
|
|
|
if flush_resp.status_code == 200:
|
|
logging.info("Data flushed successfully")
|
|
elif flush_resp.status_code == 404:
|
|
# Node is not an ingester in local mode, data will be indexed automatically
|
|
logging.info("Flush not needed (local mode), waiting for auto-indexing...")
|
|
time.sleep(3)
|
|
else:
|
|
logging.warning("Flush request returned status %s: %s", flush_resp.status_code, flush_resp.text[:200])
|
|
|
|
# Give a small buffer for flush to complete
|
|
time.sleep(2)
|
|
|
|
return True |