Files
openobserve/tests/ui-testing/playwright-tests/RUM/sourcemap-ui.spec.js
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

254 lines
9.4 KiB
JavaScript

/**
* Sourcemap UI E2E Tests
*
* Tests for RUM Error Tracking UI with sourcemap display.
*
* Test Coverage:
* - Error Tracking page navigation
* - Date range selection and query execution
* - Error list display
* - Error detail view navigation
* - Error metadata display
* - Stack trace display
*
* Prerequisites:
* - OpenObserve ENTERPRISE build running on localhost:5080
* - RUM enabled
* - RUM errors auto-ingested via global setup (rum-error-ingestion.js)
*
* Note:
* - Tests tagged with @enterprise (enterprise-only feature)
* - Pretty tab with resolved source code not yet implemented in UI
* - These tests validate basic error display functionality
*/
const { test, expect } = require('../utils/enhanced-baseFixtures.js');
const testLogger = require('../utils/test-logger.js');
const PageManager = require('../../pages/page-manager.js');
test.describe("Sourcemap UI Tests", { tag: '@enterprise' }, () => {
// Run tests in parallel for faster execution
test.describe.configure({ mode: 'parallel' });
let pm;
test.beforeEach(async ({ page }, testInfo) => {
testLogger.testStart(testInfo.title, testInfo.file);
// Navigate to base and initialize page manager
await page.goto(`${process.env.ZO_BASE_URL}?org_identifier=${process.env.ORGNAME}`);
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
pm = new PageManager(page);
testLogger.info('Test setup completed');
});
// ==========================================================================
// P0 - BASIC NAVIGATION
// ==========================================================================
test("P0: Error Tracking page loads successfully", {
tag: ['@sourcemap', '@ui', '@smoke', '@P0']
}, async ({ page }) => {
testLogger.info('Navigating to RUM Error Tracking page');
// Navigate directly to RUM Errors page
await page.goto(`${process.env.ZO_BASE_URL}/web/rum/errors?org_identifier=${process.env.ORGNAME}`);
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
await page.waitForTimeout(2000); // Allow page to stabilize
// Verify key elements are visible
await pm.rumPage.expectErrorTrackingPageLoaded();
testLogger.info('✅ Error Tracking page loaded successfully');
});
test("P0: Set date range and run query", {
tag: ['@sourcemap', '@ui', '@smoke', '@P0']
}, async ({ page }) => {
testLogger.info('Testing query execution with default time range');
// Navigate to Error Tracking page
await page.goto(`${process.env.ZO_BASE_URL}/web/rum/errors?org_identifier=${process.env.ORGNAME}`);
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(2000); // Allow page to stabilize
// Use default time range (Past 15 Minutes) which should capture recent errors
testLogger.info('Clicking Run Query button with default time range');
await pm.rumPage.clickRunQuery();
// Wait for query to execute
await pm.rumPage.waitForQueryExecution();
// Wait for results table
await pm.rumPage.expectErrorTableVisible();
testLogger.info('✅ Query executed successfully');
});
// ==========================================================================
// P1 - ERROR DISPLAY
// ==========================================================================
test("P1: Error list displays errors", {
tag: ['@sourcemap', '@ui', '@functional', '@P1']
}, async ({ page }) => {
testLogger.info('Verifying error list displays errors');
// Navigate to Error Tracking page (uses default 15 min time range)
await page.goto(`${process.env.ZO_BASE_URL}/web/rum/errors?org_identifier=${process.env.ORGNAME}`);
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(2000); // Allow page to stabilize
// Run query with default time range
await pm.rumPage.clickRunQuery();
await pm.rumPage.waitForQueryExecution();
// Wait for table to load and verify errors exist
const hasErrors = await pm.rumPage.hasErrorRows();
expect(hasErrors).toBe(true); // Fail if no errors found
// Count rows
const rowCount = await pm.rumPage.getErrorRowCount();
testLogger.info(`Found ${rowCount} error rows`);
expect(rowCount).toBeGreaterThan(0); // Fail if no rows
if (rowCount > 0) {
// Verify first row has expected content
const rowText = await pm.rumPage.getFirstErrorRowText();
testLogger.info(`First row text: ${rowText}`);
// Should contain error-related text
const hasErrorText = rowText.includes('Error') ||
rowText.includes('TypeError') ||
rowText.includes('ReferenceError') ||
rowText.includes('o2-sourcemap-test-app');
if (hasErrorText) {
testLogger.info('✅ Error list displays errors with expected content');
} else {
testLogger.warn('⚠️ Error list displayed but content may not be from test app');
}
}
});
test("P1: Click error opens detail view", {
tag: ['@sourcemap', '@ui', '@functional', '@P1']
}, async ({ page }) => {
testLogger.info('Testing error detail view navigation');
// Navigate and load errors (uses default 15 min time range)
await page.goto(`${process.env.ZO_BASE_URL}/web/rum/errors?org_identifier=${process.env.ORGNAME}`);
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(2000); // Allow page to stabilize
await pm.rumPage.clickRunQuery();
await pm.rumPage.waitForQueryExecution();
// Wait for table and verify errors exist
const hasErrors = await pm.rumPage.hasErrorRows();
expect(hasErrors).toBe(true); // Fail if no errors found
// Click first error row
testLogger.info('Clicking first error row');
await pm.rumPage.clickFirstErrorRow();
// Wait for navigation
await page.waitForURL('**/rum/errors/view/**', { timeout: 10000 }).catch(() => {
testLogger.warn('URL did not change to error detail view');
});
// Wait for detail view to load
await pm.rumPage.expectErrorDetailViewLoaded();
testLogger.info('✅ Error detail view opened successfully');
});
test("P1: Error detail shows metadata", {
tag: ['@sourcemap', '@ui', '@functional', '@P1']
}, async ({ page }) => {
testLogger.info('Verifying error detail metadata display');
// Navigate to error detail (uses default 15 min time range)
await page.goto(`${process.env.ZO_BASE_URL}/web/rum/errors?org_identifier=${process.env.ORGNAME}`);
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(2000); // Allow page to stabilize
await pm.rumPage.clickRunQuery();
await pm.rumPage.waitForQueryExecution();
const hasErrors = await pm.rumPage.hasErrorRows();
expect(hasErrors).toBe(true); // Fail if no errors found
await pm.rumPage.clickFirstErrorRow();
await pm.rumPage.expectErrorDetailViewLoaded();
// Verify metadata sections
testLogger.info('Checking for metadata sections');
// Tags section
await pm.rumPage.expectTagsSectionVisible();
// Get container text
const containerText = await pm.rumPage.getErrorViewerContainerText();
// Verify common metadata fields
const hasMetadata = containerText.includes('browser') ||
containerText.includes('Browser') ||
containerText.includes('ip') ||
containerText.includes('IP') ||
containerText.includes('service');
if (hasMetadata) {
testLogger.info('✅ Error metadata displayed correctly');
} else {
testLogger.warn('⚠️ Expected metadata fields not found');
}
});
test("P1: Stack trace displays frames", {
tag: ['@sourcemap', '@ui', '@functional', '@P1']
}, async ({ page }) => {
testLogger.info('Verifying stack trace display');
// Navigate to error detail (uses default 15 min time range)
await page.goto(`${process.env.ZO_BASE_URL}/web/rum/errors?org_identifier=${process.env.ORGNAME}`);
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(2000); // Allow page to stabilize
await pm.rumPage.clickRunQuery();
await pm.rumPage.waitForQueryExecution();
const hasErrors = await pm.rumPage.hasErrorRows();
expect(hasErrors).toBe(true); // Fail if no errors found
await pm.rumPage.clickFirstErrorRow();
await pm.rumPage.expectErrorDetailViewLoaded();
// Look for stack trace section
testLogger.info('Looking for stack trace section');
await pm.rumPage.expectStackTraceVisible();
// Get stack trace text
const frameText = await pm.rumPage.getStackTraceText();
if (frameText) {
testLogger.info(`Stack trace text: ${frameText.substring(0, 200)}...`);
// Verify stack frame format
const hasStackFrames = frameText.includes('at ') &&
(frameText.includes('.js') || frameText.includes('http')) &&
frameText.match(/:\d+:\d+/); // line:column format
if (hasStackFrames) {
testLogger.info('✅ Stack trace displays frames correctly');
} else {
testLogger.warn('⚠️ Stack trace present but format unexpected');
}
} else {
testLogger.warn('⚠️ Stack trace section not visible');
}
});
});