Files
openobserve/tests/ui-testing/playwright-tests/RUM/sourcemap-ui.spec.js
T
Bhargav 47d510aa16 feat: rebuild the RUM error detail page around issue impact (#13697)
Design at: designs/mobile-rum-sdk-evaluation/design-docs
The page showed a single error event: header, tag strip, stack trace,
replay button, event list. It could not say how bad the error was, who
it reached, whether it was new, or where it concentrated — it never
queried anything beyond that one row.

It is now a two-column triage surface: the diagnosis path (occurrences
chart, stack trace, breadcrumbs) on the left, corroborating evidence
(replay, backend trace, breakdown, who hit it) on the right.

- ErrorHeader: error type as the title with handling/source badges, the
message in an alert banner, and a context line carrying event id, route
and the service/version/env chips. Copy-permalink and copy-event-id
actions.
- ErrorImpactStrip: occurrences, users, sessions, first/last seen. A
dash, not a zero, when a figure is unavailable.
- ErrorOccurrencesChart: this signature over time, marking the bucket
that holds the event the page was opened on.
- ErrorFacetBreakdown: browser / OS / release / page as
share-of-occurrences bars, calling out any value that owns 80%+ of the
traffic — usually the fix.
- ErrorContextCard: identity, device, geo and deployment for this
occurrence. Supersedes ErrorTags, which is removed.

Backed by useErrorDetail + errorDetailQueries: an impact aggregate gates
a histogram and two facet group-bys. Three searches, staged inside the
server's work-group cap and aborted when the view is left. Facets are
split across two group-bys on purpose — one query over all four columns
returns their cross product.

Also fixes the breadcrumb trail: it read an hour of the whole stream
with no session filter, so "what the user did before the crash" could be
other people's clicks. It is now scoped to the session id.
2026-08-07 12:31:19 +00:00

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');
// Context section — who hit the error, on what device and build
await pm.rumPage.expectContextSectionVisible();
// 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');
}
});
});