Files
openobserve/tests/api-testing/tests/conftest.py
T
b1fe77871a test: add E2E tests for RUM sourcemap feature (#11014)
## 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>
2026-03-28 20:57:48 +05:30

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