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>
198 lines
6.9 KiB
Python
198 lines
6.9 KiB
Python
"""
|
|
Ingest synthetic RUM error data for sourcemap UI testing.
|
|
|
|
This script injects RUM error events with minified stacktraces that can be
|
|
resolved using uploaded sourcemaps. This allows UI tests to run without
|
|
needing to build and serve the test app.
|
|
"""
|
|
|
|
import requests
|
|
import base64
|
|
import time
|
|
import json
|
|
import os
|
|
|
|
# Get config from environment
|
|
BASE_URL = os.getenv('ZO_BASE_URL', 'http://localhost:5080')
|
|
EMAIL = os.getenv('ZO_ROOT_USER_EMAIL', 'root@example.com')
|
|
PASSWORD = os.getenv('ZO_ROOT_USER_PASSWORD', 'Complexpass#123')
|
|
ORG_ID = os.getenv('ORGNAME', 'default')
|
|
|
|
|
|
def ingest_rum_errors():
|
|
"""Ingest RUM error events to OpenObserve."""
|
|
|
|
# Create session with Basic Auth for API calls
|
|
api_session = requests.Session()
|
|
basic_auth = base64.b64encode(f"{EMAIL}:{PASSWORD}".encode()).decode()
|
|
api_session.headers.update({"Authorization": f"Basic {basic_auth}"})
|
|
|
|
# Fetch RUM token for ingestion
|
|
print("Fetching RUM token...")
|
|
rum_token_response = api_session.get(f"{BASE_URL}/api/{ORG_ID}/rumtoken")
|
|
if rum_token_response.status_code != 200:
|
|
print(f"❌ Failed to fetch RUM token: {rum_token_response.status_code}")
|
|
rum_token = None
|
|
else:
|
|
rum_token_data = rum_token_response.json()
|
|
rum_token = rum_token_data.get('data', {}).get('rum_token') or rum_token_data.get('rum_token')
|
|
if rum_token:
|
|
print(f"✅ RUM token fetched successfully")
|
|
else:
|
|
print(f"⚠️ RUM token is empty, response: {rum_token_data}")
|
|
|
|
# Create RUM ingestion session with RUM token
|
|
session = requests.Session()
|
|
if rum_token:
|
|
# RUM token goes in Authorization header as Bearer token
|
|
session.headers.update({"Authorization": f"Bearer {rum_token}"})
|
|
|
|
# Current timestamp
|
|
now = int(time.time() * 1000)
|
|
|
|
# Sample RUM error events with minified stacktraces
|
|
# These match the format that the RUM SDK sends
|
|
rum_errors = [
|
|
{
|
|
"date": now - 60000, # 1 minute ago
|
|
"type": "error",
|
|
"error": {
|
|
"message": "TypeError: Cannot read property 'name' of undefined",
|
|
"type": "TypeError",
|
|
"stack": "TypeError: Cannot read property 'name' of undefined\n at Ht @ http://localhost:8089/assets/main.87e94092.js:1:2345\n at onClick @ http://localhost:8089/assets/main.87e94092.js:1:3456",
|
|
"source": "source",
|
|
"is_crash": False,
|
|
"resource": {
|
|
"url": "http://localhost:8089/"
|
|
}
|
|
},
|
|
"service": "o2-sourcemap-test-app",
|
|
"version": "1.0.0-api-test",
|
|
"session": {
|
|
"id": "test-session-001"
|
|
},
|
|
"view": {
|
|
"id": "test-view-001",
|
|
"referrer": "",
|
|
"url": "http://localhost:8089/"
|
|
},
|
|
"application": {
|
|
"id": "o2-sourcemap-test-app"
|
|
},
|
|
"context": {
|
|
"browser": {
|
|
"name": "Chrome",
|
|
"version": "120.0.0"
|
|
},
|
|
"os": {
|
|
"name": "Mac OS",
|
|
"version": "14.0"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"date": now - 120000, # 2 minutes ago
|
|
"type": "error",
|
|
"error": {
|
|
"message": "ReferenceError: undefinedVariable is not defined",
|
|
"type": "ReferenceError",
|
|
"stack": "ReferenceError: undefinedVariable is not defined\n at triggerReferenceError @ http://localhost:8089/assets/main.87e94092.js:1:4567\n at onClick @ http://localhost:8089/assets/main.87e94092.js:1:5678",
|
|
"source": "source",
|
|
"is_crash": False,
|
|
"resource": {
|
|
"url": "http://localhost:8089/"
|
|
}
|
|
},
|
|
"service": "o2-sourcemap-test-app",
|
|
"version": "1.0.0-api-test",
|
|
"session": {
|
|
"id": "test-session-002"
|
|
},
|
|
"view": {
|
|
"id": "test-view-002",
|
|
"referrer": "",
|
|
"url": "http://localhost:8089/"
|
|
},
|
|
"application": {
|
|
"id": "o2-sourcemap-test-app"
|
|
},
|
|
"context": {
|
|
"browser": {
|
|
"name": "Firefox",
|
|
"version": "119.0"
|
|
},
|
|
"os": {
|
|
"name": "Windows",
|
|
"version": "11"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"date": now - 180000, # 3 minutes ago
|
|
"type": "error",
|
|
"error": {
|
|
"message": "RangeError: Maximum call stack size exceeded",
|
|
"type": "RangeError",
|
|
"stack": "RangeError: Maximum call stack size exceeded\n at recursiveFunction @ http://localhost:8089/assets/main.87e94092.js:1:6789\n at recursiveFunction @ http://localhost:8089/assets/main.87e94092.js:1:6789",
|
|
"source": "source",
|
|
"is_crash": False,
|
|
"resource": {
|
|
"url": "http://localhost:8089/"
|
|
}
|
|
},
|
|
"service": "o2-sourcemap-test-app",
|
|
"version": "1.0.0-api-test",
|
|
"session": {
|
|
"id": "test-session-003"
|
|
},
|
|
"view": {
|
|
"id": "test-view-003",
|
|
"referrer": "",
|
|
"url": "http://localhost:8089/"
|
|
},
|
|
"application": {
|
|
"id": "o2-sourcemap-test-app"
|
|
},
|
|
"context": {
|
|
"browser": {
|
|
"name": "Safari",
|
|
"version": "17.0"
|
|
},
|
|
"os": {
|
|
"name": "Mac OS",
|
|
"version": "14.0"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
print(f"\nIngesting {len(rum_errors)} RUM error events to {BASE_URL}")
|
|
|
|
# Use _rumdata stream (not _rumlog) because the UI queries _rumdata for errors
|
|
# The UI looks for records where type='error' in _rumdata stream
|
|
url = f"{BASE_URL}/api/{ORG_ID}/_rumdata/_json"
|
|
|
|
# Send as JSON array
|
|
response = api_session.post(
|
|
url,
|
|
json=rum_errors,
|
|
headers={'Content-Type': 'application/json'}
|
|
)
|
|
|
|
if response.status_code in [200, 201]:
|
|
print(f"✅ Successfully ingested {len(rum_errors)} RUM errors")
|
|
print(f" Status: {response.status_code}")
|
|
print(f" Stream: _rumdata (errors with type='error')")
|
|
print(f" Errors can be viewed in RUM Error Tracking page")
|
|
return True
|
|
else:
|
|
print(f"❌ Failed to ingest RUM errors")
|
|
print(f" Status: {response.status_code}")
|
|
print(f" Response: {response.text}")
|
|
return False
|
|
|
|
|
|
if __name__ == "__main__":
|
|
success = ingest_rum_errors()
|
|
exit(0 if success else 1)
|