mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
2153 lines
102 KiB
JavaScript
2153 lines
102 KiB
JavaScript
/**
|
|
* Logs Regression Bug Tests
|
|
*
|
|
* Bug fixes for logs page functionality:
|
|
* - #9996: Page appears blank midway on scroll
|
|
* - #9796: Logs page load data without clicking run query
|
|
* - #9533: Loading icon missing when run query for long duration
|
|
* - #8928: UI revamp issues (sidebar, search bar, histogram)
|
|
* - Quick mode query removed when selecting interesting field
|
|
* - Pagination not showing with histogram and SQL disabled
|
|
* - Error message should identify problematic field
|
|
* - #8224: Showing message not displayed on logs page
|
|
* - #5894: Timestamp not default selected for multi stream
|
|
* - #10344: Run query button infinite loading when switching home↔logs
|
|
* - #11469: Copy/include/exclude shown on hover in traces correlation
|
|
* - #5010: Console error when aggregating with non-existent alias
|
|
* - #5278: Pagination not showing when histogram is turned off
|
|
* - #3821: Cannot read partitions error on stream change during query
|
|
* - #6702: Partitions error when switching pages during query
|
|
* - #3131: Order by not default for distinct queries
|
|
* - #4315: Blank histogram on cancelling histogram query
|
|
* - #10821: Quick mode toggle not responding to click
|
|
* - #10595: ExpandedRow visible in 2 rows on re-query
|
|
* - #9049: Decimal values accepted in datetime picker
|
|
* - #11606: Include/exclude term added at wrong position
|
|
* - #9339: Index list collapse/expand doesn't redraw histogram
|
|
* - #7310: Search term appended instead of replaced in streams
|
|
* - #5277: Column positions change back after re-query
|
|
* - #4426: Text wrap causes misalignment on saved view switch
|
|
* - #4091: Cancel query on visualize page
|
|
*/
|
|
|
|
const { test, expect, navigateToBase } = require('../../utils/enhanced-baseFixtures.js');
|
|
const testLogger = require('../../utils/test-logger.js');
|
|
const PageManager = require('../../../pages/page-manager.js');
|
|
const logData = require("../../../fixtures/log.json");
|
|
const { ingestTestData, sendRequest, getHeaders, getIngestionUrl, waitForFieldValueSearchable } = require('../../utils/data-ingestion.js');
|
|
const { getOrgIdentifier, isCloudEnvironment } = require('../../utils/cloud-auth.js');
|
|
|
|
// Auto-derives to a bar chart with X+Y populated, so a saved chart type of "line"
|
|
// is provably restored config rather than config re-derived from this query.
|
|
const BUILDER_QUERY =
|
|
'SELECT histogram(_timestamp) as "x_axis_1", count(*) as "y_axis_1" FROM "e2e_automate" GROUP BY x_axis_1';
|
|
|
|
/**
|
|
* Build a chart on the Build tab and save it as a saved view.
|
|
* The view must be saved while the Build tab is open — SearchBar only persists
|
|
* buildData when logsVisualizeToggle === 'build'.
|
|
*/
|
|
async function createBuilderSavedView(pm, page, viewName, chartType) {
|
|
await pm.logsPage.enableSqlModeIfNeeded();
|
|
await pm.logsPage.setQueryEditorContent(BUILDER_QUERY);
|
|
await pm.logsPage.runQueryAndWaitForResults();
|
|
|
|
await pm.logsPage.clickBuildToggle();
|
|
const loaded = await pm.logsPage.waitForBuildTabLoaded();
|
|
expect(loaded, 'Build tab must finish initializing before saving the view').toBeTruthy();
|
|
|
|
await pm.logsPage.selectChartType(chartType);
|
|
await pm.logsPage.verifyChartTypeSelected(chartType);
|
|
|
|
// clickSaveViewButton opens the utilities menu itself, so the list dialog is not
|
|
// opened here — clickSaveViewButton would only have to close it again.
|
|
await pm.logsPage.clickSaveViewButton();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.fillSavedViewName(viewName);
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.clickSavedViewDialogSave();
|
|
await page.waitForTimeout(2000);
|
|
testLogger.info(`Builder saved view created: ${viewName} (chart type ${chartType})`);
|
|
}
|
|
|
|
async function applySavedViewByName(pm, page, viewName) {
|
|
await pm.logsPage.clickSavedViewsExpand();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.clickSavedViewSearchInput();
|
|
await pm.logsPage.fillSavedViewSearchInput(viewName);
|
|
await page.waitForTimeout(1000);
|
|
await pm.logsPage.waitForSavedViewText(viewName);
|
|
await pm.logsPage.clickSavedViewByText(viewName);
|
|
await page.waitForTimeout(3000);
|
|
testLogger.info(`Applied saved view: ${viewName}`);
|
|
}
|
|
|
|
async function deleteSavedViewQuietly(pm, page, viewName) {
|
|
try {
|
|
await pm.logsPage.clickDeleteSavedViewButton(viewName).catch(() => {});
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.clickConfirmButton().catch(() => {});
|
|
testLogger.info(`Cleaned up saved view: ${viewName}`);
|
|
} catch (cleanupError) {
|
|
testLogger.debug(`Saved view cleanup skipped: ${viewName}`);
|
|
}
|
|
}
|
|
|
|
test.describe("Logs Regression Bug Fixes", () => {
|
|
// Changed from serial to parallel - tests are independent (each gets own page/PM in beforeEach)
|
|
test.describe.configure({ mode: 'parallel' });
|
|
let pm;
|
|
// Stream names for field cache test - declared at describe scope for afterEach cleanup
|
|
let fieldCacheStreamsToCleanup = [];
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Attempt data ingestion but don't fail test if it errors (global setup already ingested data)
|
|
try {
|
|
await ingestTestData(page);
|
|
} catch (error) {
|
|
testLogger.warn(`Data ingestion skipped (may already exist from global setup): ${error.message}`);
|
|
}
|
|
|
|
testLogger.info('Logs regression bug test setup completed');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #9996: Page appears blank midway on scroll
|
|
// https://github.com/openobserve/openobserve/issues/9996
|
|
// ==========================================================================
|
|
test("should maintain table visibility during scroll @bug-9996 @P0 @scroll @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify scroll maintains content visibility (Bug #9996)');
|
|
|
|
await pm.logsPage.navigateToLogs();
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await pm.logsPage.clickRefreshButton();
|
|
|
|
// The grid must hold rows before scrolling, or the scroll assertions prove nothing.
|
|
await pm.logsPage.expectResultsGridSettledWithRows();
|
|
|
|
const startScrollTop = await pm.logsPage.getResultsScrollTop();
|
|
expect(startScrollTop, 'results area must expose a scrollable container').not.toBeNull();
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
await pm.logsPage.scrollResultsGrid();
|
|
// The reported symptom is a blank grid, so rows are the assertion, not the container.
|
|
await pm.logsPage.expectResultsGridStillRendersRows();
|
|
testLogger.info(`Rows still rendered after scroll ${i + 1}/5`);
|
|
}
|
|
|
|
// Without this the loop would pass on a wheel that never moved the results area.
|
|
const endScrollTop = await pm.logsPage.getResultsScrollTop();
|
|
expect(endScrollTop, 'results area must actually scroll, otherwise nothing was tested')
|
|
.toBeGreaterThan(startScrollTop);
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #9796: Logs page load data without clicking on run query
|
|
// https://github.com/openobserve/openobserve/issues/9796
|
|
// ==========================================================================
|
|
test("should only load data after clicking run query @bug-9796 @P0 @queryBehavior @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify no auto-load on stream selection (Bug #9796)');
|
|
|
|
// Track search API calls
|
|
const searchApiCalls = [];
|
|
await page.route('**/api/*/_search**', async (route, request) => {
|
|
const url = request.url();
|
|
if (!url.includes('validate=true') && !url.includes('_values')) {
|
|
searchApiCalls.push({ url, timestamp: Date.now() });
|
|
testLogger.info(`Search API called: ${url.substring(0, 100)}`);
|
|
}
|
|
await route.continue();
|
|
});
|
|
|
|
// Navigate to logs page
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
const initialCallCount = searchApiCalls.length;
|
|
testLogger.info(`Initial API calls: ${initialCallCount}`);
|
|
|
|
// Select stream WITHOUT clicking run query
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(2000);
|
|
|
|
const callsAfterSelection = searchApiCalls.length - initialCallCount;
|
|
testLogger.info(`API calls after stream selection: ${callsAfterSelection}`);
|
|
|
|
// Now click Run Query
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// STRONG ASSERTION: Search API should be called after clicking run query
|
|
const totalCalls = searchApiCalls.length;
|
|
expect(totalCalls).toBeGreaterThan(initialCallCount);
|
|
testLogger.info(`Total API calls after Run Query: ${totalCalls}`);
|
|
|
|
// STRONG ASSERTION: Results should load after explicit query
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
|
|
testLogger.info('✓ PASSED: Data loads only after clicking Run Query');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #9533: Loading icon missing when run query for long duration
|
|
// https://github.com/openobserve/openobserve/issues/9533
|
|
// ==========================================================================
|
|
test("should show visual feedback during query execution @bug-9533 @P0 @loading @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify loading indicator during query (Bug #9533)');
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Set larger time range
|
|
await pm.logsPage.clickDateTimeButton();
|
|
await pm.logsPage.clickRelative1HourOrFallback();
|
|
|
|
// STRONG ASSERTION: Button should be enabled before query
|
|
await pm.logsPage.expectRefreshButtonEnabled();
|
|
|
|
// Click run query
|
|
await pm.logsPage.clickRefreshButton();
|
|
|
|
// Wait for query to complete
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// STRONG ASSERTION: Button should be enabled after query completes
|
|
await pm.logsPage.expectRefreshButtonEnabled();
|
|
|
|
// STRONG ASSERTION: Results should be visible after query
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
|
|
testLogger.info('✓ PASSED: Query execution provides feedback');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #8928: UI revamp issues - Search bar UI consistency
|
|
// https://github.com/openobserve/openobserve/issues/8928
|
|
// ==========================================================================
|
|
test("should display search bar with required UI elements @bug-8928 @P1 @ui @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify search bar UI consistency (Bug #8928)');
|
|
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// STRONG ASSERTION: Stream selector must be visible
|
|
await pm.logsPage.expectStreamSelectorVisible();
|
|
|
|
// STRONG ASSERTION: Refresh button must be visible
|
|
await pm.logsPage.expectRefreshButtonVisible();
|
|
|
|
// STRONG ASSERTION: DateTime button must be visible
|
|
await pm.logsPage.expectDateTimeButtonVisible();
|
|
|
|
testLogger.info('✓ PASSED: Search bar UI elements verified');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #8928: UI revamp issues - Histogram rendering
|
|
// https://github.com/openobserve/openobserve/issues/8928
|
|
// ==========================================================================
|
|
test("should render histogram without cropping @bug-8928 @P1 @ui @histogram @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify histogram renders correctly (Bug #8928)');
|
|
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Toggle histogram on
|
|
await pm.logsPage.enableHistogram();
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// STRONG ASSERTION: Histogram should be visible
|
|
await pm.logsPage.expectHistogramVisible();
|
|
|
|
// Toggle off and back on
|
|
await pm.logsPage.toggleHistogram();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.toggleHistogram();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// STRONG ASSERTION: Histogram should still be visible after toggle
|
|
await pm.logsPage.expectHistogramVisible();
|
|
|
|
testLogger.info('✓ PASSED: Histogram rendering verified');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug: Quick mode query removed when selecting interesting field
|
|
// ==========================================================================
|
|
test("should preserve quick mode query when selecting a new interesting field @quickMode @P0 @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify quick mode query is preserved when selecting new interesting field');
|
|
|
|
// Navigate to logs page
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Select stream
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Enable quick mode if not already enabled
|
|
await pm.logsPage.enableQuickModeIfDisabled();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Click all fields button to show field list
|
|
await pm.logsPage.clickAllFieldsButton();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Write a query in the query editor using a field that exists in e2e_automate stream
|
|
// Using 'code' field which is guaranteed to exist per log.json fixture
|
|
const testQuery = 'code';
|
|
await pm.logsPage.clickQueryEditor();
|
|
await pm.logsPage.typeInQueryEditor(testQuery);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Get the query text before selecting interesting field
|
|
const queryBeforeSelection = await pm.logsPage.getQueryEditorText();
|
|
testLogger.info(`Query before selecting interesting field: ${queryBeforeSelection}`);
|
|
|
|
// Search for a field and click on it as interesting field
|
|
// Using 'stream' field which is guaranteed to exist per log.json fixture
|
|
await pm.logsPage.fillIndexFieldSearchInput("stream");
|
|
await pm.logsPage.clickInterestingFieldButton("stream");
|
|
await page.waitForTimeout(500);
|
|
|
|
// Get the query text after selecting interesting field
|
|
const queryAfterSelection = await pm.logsPage.getQueryEditorText();
|
|
testLogger.info(`Query after selecting interesting field: ${queryAfterSelection}`);
|
|
|
|
// STRONG ASSERTION: The original query should still be present in the editor
|
|
expect(queryAfterSelection).toContain(testQuery);
|
|
|
|
testLogger.info('✓ PASSED: Quick mode query preserved when selecting interesting field');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug: Pagination not showing with histogram & SQL disabled
|
|
// ==========================================================================
|
|
test("should show pagination when histogram and SQL mode are disabled @pagination @P0 @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify pagination shows when histogram and SQL mode are disabled');
|
|
|
|
// Navigate to logs page
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Select stream
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Disable histogram if enabled
|
|
await pm.logsPage.ensureHistogramToggleState(false);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Ensure SQL mode is disabled
|
|
await pm.logsPage.disableSqlModeIfNeeded();
|
|
|
|
// Set time range to ensure we have enough data
|
|
await pm.logsPage.clickDateTimeButton();
|
|
await pm.logsPage.clickRelative15MinButton();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Run query
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Wait for logs table to be visible
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
|
|
// STRONG ASSERTION: Result pagination should be visible
|
|
await pm.logsPage.expectResultPaginationVisible();
|
|
testLogger.info('Result pagination is visible');
|
|
|
|
// STRONG ASSERTION: SQL pagination should NOT be visible when SQL mode is off
|
|
await pm.logsPage.expectSQLPaginationNotVisible();
|
|
|
|
testLogger.info('✓ PASSED: Pagination shows correctly when histogram and SQL mode are disabled');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug: Error message should identify problematic field
|
|
// ==========================================================================
|
|
test("should show correct error message identifying the problematic field @errorMessage @P0 @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify error message correctly identifies the problematic field');
|
|
|
|
// Navigate to logs page
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Select stream
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Enable SQL mode to write custom query
|
|
await pm.logsPage.enableSqlModeIfNeeded();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Write a query with a non-existent field
|
|
const nonExistentField = 'nonexistent_field_xyz_12345';
|
|
const query = `SELECT ${nonExistentField} FROM "e2e_automate"`;
|
|
await pm.logsPage.clearAndFillQueryEditor(query);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Run query
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(3000);
|
|
|
|
// STRONG ASSERTION: Error message should be visible
|
|
await pm.logsPage.expectErrorMessageVisible();
|
|
testLogger.info('Error message is visible');
|
|
|
|
// Get the detailed error message using page object method
|
|
const errorDialogText = await pm.logsPage.getDetailedErrorDialogText();
|
|
testLogger.info(`Full error dialog text: ${errorDialogText}`);
|
|
|
|
const fullErrorText = errorDialogText.toLowerCase();
|
|
|
|
// STRONG ASSERTION: Error message MUST contain the exact problematic field name
|
|
// This ensures the error is specific and actionable, not just a generic "field not found"
|
|
expect(fullErrorText).toContain(nonExistentField.toLowerCase());
|
|
testLogger.info(`Verified error message contains the problematic field: ${nonExistentField}`);
|
|
|
|
testLogger.info('✓ PASSED: Error message correctly identifies problematic field');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #10821: Quick mode text label click not working
|
|
// https://github.com/openobserve/openobserve/issues/10821
|
|
// ==========================================================================
|
|
test("should toggle quick mode when clicking on text label @bug-10821 @P3 @quickMode @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify quick mode toggles via text label click (Bug #10821)');
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Get initial quick mode state
|
|
const initialState = await pm.logsPage.getQuickModeState();
|
|
testLogger.info(`Initial quick mode state: ${initialState}`);
|
|
|
|
// Click on the text label (not the toggle switch)
|
|
await pm.logsPage.clickQuickModeTextLabel();
|
|
testLogger.info('Clicked on quick mode text label');
|
|
|
|
// Wait for toggle to update
|
|
await page.waitForTimeout(500);
|
|
|
|
// Get new state
|
|
const newState = await pm.logsPage.getQuickModeState();
|
|
testLogger.info(`New quick mode state: ${newState}`);
|
|
|
|
// STRONG ASSERTION: Verify state changed
|
|
expect(newState).not.toBe(initialState);
|
|
testLogger.info('✓ Quick mode state changed after clicking text label');
|
|
|
|
// Toggle back by clicking text label again
|
|
await pm.logsPage.clickQuickModeTextLabel();
|
|
await page.waitForTimeout(500);
|
|
|
|
const finalState = await pm.logsPage.getQuickModeState();
|
|
testLogger.info(`Final quick mode state: ${finalState}`);
|
|
|
|
// STRONG ASSERTION: Verify it toggled back
|
|
expect(finalState).toBe(initialState);
|
|
testLogger.info('✓ PASSED: Quick mode toggled back successfully via text label');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #11041: Multi-select behavior incorrect from sidebar
|
|
// https://github.com/openobserve/openobserve/issues/11041
|
|
// ==========================================================================
|
|
test.skip("should disable include button for already-included field values @bug-11041 @P2 @includeExclude @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify multi-select sidebar behavior (Bug #11041)');
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await pm.logsPage.clickDateTimeButton();
|
|
await pm.logsPage.clickRelative15MinButton();
|
|
|
|
// Run initial query to get log results
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Verify logs table is visible
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
testLogger.info('✓ Initial query completed, logs table visible');
|
|
|
|
// Find an available field to expand - try common fields that exist in e2e_automate
|
|
const testFields = ['kubernetes_pod_name', 'log', 'code', '_timestamp'];
|
|
let selectedField = null;
|
|
let fieldExpandButton = null;
|
|
|
|
for (const fieldName of testFields) {
|
|
const fieldBtn = pm.logsPage.getFieldExpandButton(fieldName);
|
|
if (await fieldBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
selectedField = fieldName;
|
|
fieldExpandButton = fieldBtn;
|
|
testLogger.info(`✓ Found available field: ${fieldName}`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!selectedField) {
|
|
throw new Error('Bug #11041: No suitable field found to test with');
|
|
}
|
|
|
|
// Expand the field in the sidebar to see field values
|
|
await expect(fieldExpandButton, `Bug #11041: ${selectedField} field must be visible`).toBeVisible({ timeout: 5000 });
|
|
await fieldExpandButton.click();
|
|
await page.waitForTimeout(500);
|
|
testLogger.info(`✓ Expanded ${selectedField} field in sidebar`);
|
|
|
|
// Find and click include button for a field value
|
|
const includeBtn = pm.logsPage.getSubfieldListEqualButton(selectedField).first();
|
|
await expect(includeBtn, 'Bug #11041: include button should be visible').toBeVisible({ timeout: 3000 });
|
|
await includeBtn.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Click "Include Search Term" from the menu - try multiple possible menu texts
|
|
const possibleMenuTexts = ['Include Search Term', 'Include', 'Add to filter'];
|
|
let includeMenuItem = null;
|
|
|
|
for (const menuText of possibleMenuTexts) {
|
|
const menuItem = pm.logsPage.getMenuItemByText(menuText);
|
|
if (await menuItem.isVisible({ timeout: 1000 }).catch(() => false)) {
|
|
includeMenuItem = menuItem;
|
|
testLogger.info(`✓ Found include menu item with text: ${menuText}`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!includeMenuItem) {
|
|
// Post-migration the action is on an ODropdown rendered via JsonPreview;
|
|
// its include item carries data-test="log-details-include-field-btn".
|
|
const anyMenuItem = pm.logsPage.getIncludeSearchTermMenuItem();
|
|
if (await anyMenuItem.isVisible({ timeout: 1000 }).catch(() => false)) {
|
|
includeMenuItem = anyMenuItem;
|
|
testLogger.info('✓ Using first visible menu item');
|
|
} else {
|
|
throw new Error('Bug #11041: include menu item not found with any selector');
|
|
}
|
|
}
|
|
|
|
await expect(includeMenuItem, 'Bug #11041: include menu item should be visible').toBeVisible({ timeout: 3000 });
|
|
await includeMenuItem.click();
|
|
testLogger.info('✓ Added first include search term');
|
|
|
|
// Run the query with the include filter
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// BUG CHECK: The include button should now be disabled or show different state
|
|
// for the already-included value
|
|
const queryEditor = pm.logsPage.getQueryEditorLocator();
|
|
const queryText = await queryEditor.textContent();
|
|
testLogger.info(`Query editor contains: ${queryText}`);
|
|
|
|
// STRONG ASSERTION: Verify the include term is in the query
|
|
expect(queryText).toContain(selectedField);
|
|
testLogger.info('✓ Include term is present in query');
|
|
|
|
// Try clicking the same include button again - it should either:
|
|
// 1. Be disabled, OR
|
|
// 2. Not add duplicate entries
|
|
await fieldExpandButton.click().catch(() => {});
|
|
await page.waitForTimeout(500);
|
|
|
|
// Check if we can still click the same value's include button
|
|
// (This tests if spamming is prevented)
|
|
const includeBtn2 = pm.logsPage.getSubfieldListEqualButton(selectedField).first();
|
|
|
|
// Assert button is still visible after adding filter
|
|
await expect(includeBtn2, 'Bug #11041: include button should remain visible after adding filter').toBeVisible({ timeout: 3000 });
|
|
|
|
const isDisabled = await includeBtn2.isDisabled().catch(() => false);
|
|
testLogger.info(`Include button disabled state: ${isDisabled}`);
|
|
|
|
// PRIMARY ASSERTION: The button should be disabled for already-included values
|
|
expect(isDisabled, 'Bug #11041: include button should be disabled for already-included value').toBe(true);
|
|
|
|
testLogger.info('✓ PASSED: Multi-select sidebar behavior test completed');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Field Values Cache Bug: Field values not refreshed when switching streams
|
|
// When switching between streams with same field name, values should refresh
|
|
// Bug: UI displays cached values from previous stream instead of fetching new values
|
|
// ==========================================================================
|
|
test("should fetch fresh field values when switching streams @fieldValuesCache @P1 @regression", async ({ page }) => {
|
|
testLogger.info('Test: Field values should refresh when switching between streams');
|
|
|
|
// Strategy: use existing e2e_automate stream (always available) + ONE new stream.
|
|
// Alpha1 cloud has a backend bug where creating 2 new streams in quick succession
|
|
// causes the second stream's metadata to never appear in the streams API.
|
|
// By reusing e2e_automate, we only create 1 new stream (which always works).
|
|
const stream1Name = 'e2e_automate'; // existing stream from global setup
|
|
const runId = Date.now();
|
|
const stream2Name = `e2e_svctest_${runId}`;
|
|
// Only the new stream needs cleanup
|
|
fieldCacheStreamsToCleanup = [stream2Name];
|
|
const testFieldName = 'service_name';
|
|
const stream1Value = `svc-automate-${runId}`;
|
|
const stream2Value = `svc-newstream-${runId}`;
|
|
|
|
const streamWaitMs = isCloudEnvironment() ? 150000 : 30000;
|
|
|
|
// Step 1: Ingest data with unique service_name into e2e_automate (stream already exists)
|
|
// IMPORTANT: Use sendRequest (page.request.post) instead of pm.logsPage.ingestData (node-fetch).
|
|
// On cloud, streams ingested via node-fetch never appear in the streams API keyword search,
|
|
// while streams ingested via page.request are indexed immediately.
|
|
const orgId = getOrgIdentifier() || 'default';
|
|
const headers = getHeaders();
|
|
testLogger.info(`Ingesting data into ${stream1Name} with ${testFieldName}=${stream1Value}`);
|
|
const timestamp1 = Date.now() * 1000;
|
|
const stream1Url = getIngestionUrl(orgId, stream1Name);
|
|
const stream1Response = await sendRequest(page, stream1Url, [
|
|
{ [testFieldName]: stream1Value, level: 'info', message: 'Field cache test stream1', _timestamp: timestamp1 },
|
|
{ [testFieldName]: stream1Value, level: 'info', message: 'Field cache test stream1 2', _timestamp: timestamp1 + 1000000 },
|
|
], headers);
|
|
testLogger.info(`Stream1 ingestion response: ${JSON.stringify(stream1Response)}`);
|
|
|
|
// Readiness gate: e2e_automate is a SHARED stream and freshly-ingested data is not
|
|
// queryable immediately (WAL -> index lag). Without waiting on the actual value, the
|
|
// field-values panel returns svc-automate-* values from earlier runs/retries but misses
|
|
// the one we just ingested, making the stream1 assertion flaky. Wait until it's searchable.
|
|
const stream1Searchable = await waitForFieldValueSearchable(page, stream1Name, testFieldName, stream1Value, streamWaitMs, 3000);
|
|
expect(stream1Searchable, `Value ${stream1Value} should be searchable in ${stream1Name} via API`).toBeTruthy();
|
|
testLogger.info(`Value ${stream1Value} confirmed searchable in ${stream1Name}`);
|
|
|
|
// Step 2: Create new stream by ingesting standard test data first (ensures proper stream registration),
|
|
// then ingest custom records with distinct service_name for the field values cache test.
|
|
testLogger.info(`Creating stream ${stream2Name} with standard test data`);
|
|
await ingestTestData(page, stream2Name);
|
|
testLogger.info(`Ingesting custom data into ${stream2Name} with ${testFieldName}=${stream2Value}`);
|
|
const timestamp2 = timestamp1 + 2000000;
|
|
const stream2Url = getIngestionUrl(orgId, stream2Name);
|
|
const stream2Response = await sendRequest(page, stream2Url, [
|
|
{ [testFieldName]: stream2Value, level: 'info', log: 'Field cache test stream2', _timestamp: timestamp2 },
|
|
{ [testFieldName]: stream2Value, level: 'info', log: 'Field cache test stream2 2', _timestamp: timestamp2 + 1000000 },
|
|
], headers);
|
|
testLogger.info(`Stream2 custom ingestion response: ${JSON.stringify(stream2Response)}`);
|
|
|
|
// Wait for the new stream to be indexed (e2e_automate already exists)
|
|
testLogger.info(`Waiting for ${stream2Name} to be indexed...`);
|
|
const stream2Available = await pm.logsPage.waitForStreamAvailable(stream2Name, streamWaitMs, 3000);
|
|
expect(stream2Available, `Stream ${stream2Name} should be available via API`).toBeTruthy();
|
|
testLogger.info(`Stream ${stream2Name} confirmed available via API`);
|
|
|
|
// Readiness gate for the custom value too: waitForStreamAvailable only confirms the
|
|
// stream's metadata exists, not that our distinct svc-newstream value is searchable yet.
|
|
const stream2Searchable = await waitForFieldValueSearchable(page, stream2Name, testFieldName, stream2Value, streamWaitMs, 3000);
|
|
expect(stream2Searchable, `Value ${stream2Value} should be searchable in ${stream2Name} via API`).toBeTruthy();
|
|
testLogger.info(`Value ${stream2Value} confirmed searchable in ${stream2Name}`);
|
|
|
|
// Step 3: Navigate to logs and select stream1 (e2e_automate — always available)
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await pm.logsPage.selectStream(stream1Name);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Set time range and run query
|
|
await pm.logsPage.clickDateTimeButton();
|
|
await pm.logsPage.clickRelative15MinButton();
|
|
|
|
// Wait for search API response after clicking refresh
|
|
const searchResponsePromise1 = page.waitForResponse(
|
|
response => response.url().includes('/_search') && response.status() === 200,
|
|
{ timeout: 30000 }
|
|
);
|
|
await pm.logsPage.clickRefreshButton();
|
|
await searchResponsePromise1;
|
|
// Wait for results table to fully render before field interaction
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Step 4: Expand the service_name field and capture values from stream1
|
|
testLogger.info(`Expanding ${testFieldName} field in ${stream1Name}`);
|
|
await pm.logsPage.fillIndexFieldSearchInput(testFieldName);
|
|
await pm.logsPage.waitForFieldExpandButtonVisible(testFieldName);
|
|
|
|
// Wait for field values API response after expanding
|
|
const valuesResponsePromise1 = page.waitForResponse(
|
|
response => response.url().includes('/_values') && response.status() === 200,
|
|
{ timeout: 20000 }
|
|
);
|
|
await pm.logsPage.clickFieldExpandButton(testFieldName);
|
|
await valuesResponsePromise1;
|
|
await pm.logsPage.waitForFieldValues(testFieldName, 15000);
|
|
|
|
// Get field values from stream1
|
|
const stream1FieldValues = await pm.logsPage.getFieldValuesText(testFieldName);
|
|
testLogger.info(`Stream1 field values: ${JSON.stringify(stream1FieldValues)}`);
|
|
|
|
// Verify stream1 values contain our unique value (may have count suffix in UI)
|
|
const hasStream1Value = stream1FieldValues.some(v => v.startsWith(stream1Value));
|
|
expect(hasStream1Value, `Expected stream1 values to contain ${stream1Value}, got: ${stream1FieldValues}`).toBeTruthy();
|
|
testLogger.info(`✓ Stream1 shows correct value: ${stream1Value}`);
|
|
|
|
// Collapse the field
|
|
await pm.logsPage.collapseField(testFieldName);
|
|
|
|
// Step 5: Switch to stream2 WITHOUT refreshing the page
|
|
testLogger.info(`Switching to ${stream2Name} without page refresh`);
|
|
await pm.logsPage.selectStream(stream2Name);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Run query for stream2 and wait for search API response
|
|
const searchResponsePromise2 = page.waitForResponse(
|
|
response => response.url().includes('/_search') && response.status() === 200,
|
|
{ timeout: 30000 }
|
|
);
|
|
await pm.logsPage.clickRefreshButton();
|
|
await searchResponsePromise2;
|
|
// Wait for results table to fully render before field interaction
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Step 6: Expand the same service_name field in stream2
|
|
testLogger.info(`Expanding ${testFieldName} field in ${stream2Name}`);
|
|
await pm.logsPage.fillIndexFieldSearchInput(testFieldName);
|
|
await pm.logsPage.waitForFieldExpandButtonVisible(testFieldName);
|
|
|
|
// Wait for field values API response after expanding
|
|
const valuesResponsePromise2 = page.waitForResponse(
|
|
response => response.url().includes('/_values') && response.status() === 200,
|
|
{ timeout: 20000 }
|
|
);
|
|
await pm.logsPage.clickFieldExpandButton(testFieldName);
|
|
await valuesResponsePromise2;
|
|
await pm.logsPage.waitForFieldValues(testFieldName, 15000);
|
|
|
|
// Get field values from stream2
|
|
const stream2FieldValues = await pm.logsPage.getFieldValuesText(testFieldName);
|
|
testLogger.info(`Stream2 field values: ${JSON.stringify(stream2FieldValues)}`);
|
|
|
|
// PRIMARY ASSERTION: Stream2 values should contain stream2 unique value (may have count suffix)
|
|
const hasStream2Value = stream2FieldValues.some(v => v.startsWith(stream2Value));
|
|
expect(hasStream2Value, `Expected stream2 values to contain ${stream2Value}, got: ${stream2FieldValues}`).toBeTruthy();
|
|
testLogger.info(`✓ Stream2 shows correct value: ${stream2Value}`);
|
|
|
|
// SECONDARY ASSERTION: Stream2 values should NOT contain stream1 cached values
|
|
const hasCachedStream1Value = stream2FieldValues.some(v => v.startsWith(stream1Value));
|
|
expect(hasCachedStream1Value, `Stream2 should not have stream1 cached values, got: ${stream2FieldValues}`).toBeFalsy();
|
|
testLogger.info('✓ Stream2 does NOT show cached values from stream1');
|
|
|
|
testLogger.info('✓ PASSED: Field values correctly refresh when switching streams (no caching issue)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #9788: Share button should be disabled when ZO_WEB_URL is not configured
|
|
// https://github.com/openobserve/openobserve/issues/9788
|
|
// ==========================================================================
|
|
test("Share button should be disabled when ZO_WEB_URL is not configured @bug-9788 @P1 @shareLink @regression", async ({ page }, testInfo) => {
|
|
testLogger.info('Test: Share button disabled state with mocked config (Bug #9788)');
|
|
|
|
// Set up mock BEFORE any navigation to ensure config is mocked from the start
|
|
await page.route('**/config', async (route) => {
|
|
const response = await route.fetch();
|
|
const json = await response.json();
|
|
|
|
// Delete web_url property to simulate it not being configured
|
|
const modifiedConfig = { ...json };
|
|
delete modifiedConfig.web_url;
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(modifiedConfig)
|
|
});
|
|
});
|
|
|
|
// Navigate to logs page with mock already active
|
|
const logsUrl = `${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`;
|
|
testLogger.info('Navigating to logs page with mocked config');
|
|
await page.goto(logsUrl);
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
// Select a stream and run query to load results
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
testLogger.info('Logs page loaded with mocked config');
|
|
|
|
// PRIMARY ASSERTION: Share button should exist
|
|
await pm.logsPage.expectShareLinkButtonVisible();
|
|
testLogger.info('✓ Share button is visible');
|
|
|
|
// SECONDARY ASSERTION: Share button should be disabled when ZO_WEB_URL not configured
|
|
await pm.logsPage.expectShareLinkButtonDisabled();
|
|
testLogger.info('✓ PRIMARY CHECK PASSED: Share button is disabled (ZO_WEB_URL not configured)');
|
|
|
|
// TERTIARY ASSERTION: Check for tooltip explaining why it's disabled
|
|
await pm.logsPage.hoverShareLinkButton();
|
|
|
|
// Verify tooltip is visible with specific message about ZO_WEB_URL configuration
|
|
await pm.logsPage.expectShareLinkTooltipVisible(/share\s+url\s+is\s+disabled.*zo_web_url.*configured/i);
|
|
|
|
// Get and validate tooltip text matches expected structure
|
|
const tooltipText = await pm.logsPage.getShareLinkTooltipText(/share\s+url\s+is\s+disabled.*zo_web_url.*configured/i);
|
|
expect(tooltipText.toLowerCase()).toMatch(/share\s+url\s+is\s+disabled.*zo_web_url.*configured/i);
|
|
testLogger.info('✓ TERTIARY CHECK PASSED: Informative tooltip present');
|
|
|
|
testLogger.info('✓ PASSED: Share button disabled state test completed for Bug #9788');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #9690: VRL function not loading correctly when opening saved view
|
|
// https://github.com/openobserve/openobserve/issues/9690
|
|
// ==========================================================================
|
|
test("should load VRL function correctly when opening saved view @bug-9690 @P1 @savedViews @vrl @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify VRL function loads correctly in saved views (Bug #9690)');
|
|
|
|
const uniqueSuffix = Date.now();
|
|
const testFunctionName = `TestVRLFunc_${uniqueSuffix}`;
|
|
const testViewName = `VRLTestView_${uniqueSuffix}`;
|
|
const vrlFunction = '.test_field = "bug9690_test"';
|
|
|
|
try {
|
|
// Navigate to logs page
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Not wrapped in a catch: if the editor cannot be opened this test has
|
|
// nothing to assert, and swallowing that is what made it pass for months
|
|
// without exercising VRL at all.
|
|
testLogger.info('Step 1: Enabling VRL function toggle');
|
|
await pm.logsPage.clickVrlToggleButton();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Step 2: Enter VRL function in the editor
|
|
testLogger.info('Step 2: Entering VRL function');
|
|
const vrlEditor = pm.logsPage.getVrlEditor().first();
|
|
|
|
// STRONG ASSERTION: VRL editor must be visible
|
|
await expect(vrlEditor, 'Bug #9690: VRL editor must be visible').toBeVisible({ timeout: 5000 });
|
|
await vrlEditor.click();
|
|
await page.keyboard.type(vrlFunction);
|
|
testLogger.info(`VRL function entered: ${vrlFunction}`);
|
|
|
|
// Step 3: Save the function
|
|
try {
|
|
await pm.logsPage.clickSaveTransformButton();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.fillSavedFunctionNameInput(testFunctionName);
|
|
await pm.logsPage.clickConfirmButton();
|
|
await page.waitForTimeout(1000);
|
|
testLogger.info(`Function saved: ${testFunctionName}`);
|
|
} catch (saveError) {
|
|
testLogger.warn('Save function step skipped - UI flow may differ');
|
|
}
|
|
|
|
// Step 4: Run query to have results
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Step 5: Save the view
|
|
testLogger.info('Step 5: Saving current view');
|
|
await pm.logsPage.clickSavedViewsExpand();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.clickSaveViewButton();
|
|
await pm.logsPage.fillSavedViewName(testViewName);
|
|
await pm.logsPage.clickSavedViewDialogSave();
|
|
await page.waitForTimeout(2000);
|
|
testLogger.info(`View saved: ${testViewName}`);
|
|
|
|
// Step 6: Reload the page to simulate fresh load
|
|
testLogger.info('Step 6: Reloading page');
|
|
await page.reload();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Step 7: Navigate back to logs and select the saved view
|
|
testLogger.info('Step 7: Selecting saved view');
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Open saved views dropdown and search
|
|
await pm.logsPage.clickSavedViewsExpand();
|
|
await pm.logsPage.clickSavedViewSearchInput();
|
|
await pm.logsPage.fillSavedViewSearchInput(testViewName);
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Wait for and click on the saved view
|
|
await pm.logsPage.waitForSavedViewText(testViewName);
|
|
await pm.logsPage.clickSavedViewByText(testViewName);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Step 8: Verify VRL function is loaded
|
|
testLogger.info('Step 8: Verifying VRL function loaded');
|
|
|
|
// Restoring a saved view that carried a function re-opens the editor by
|
|
// itself, so this must be idempotent — the old blind toggle closed it and
|
|
// then waited for it to be visible.
|
|
await pm.logsPage.ensureVrlEditorOpen();
|
|
|
|
// Check if VRL editor has content
|
|
const vrlEditorContent = await pm.logsPage.getVrlEditorContent();
|
|
testLogger.info(`VRL editor content after load: ${vrlEditorContent.substring(0, 100)}`);
|
|
|
|
// Check if function dropdown shows selection
|
|
const dropdownText = await pm.logsPage.getFunctionDropdownText();
|
|
testLogger.info(`Function dropdown text: ${dropdownText}`);
|
|
|
|
// PRIMARY ASSERTION: VRL content should be present (not empty)
|
|
// Either the editor has content OR the function is selected in dropdown
|
|
const hasVrlContent = vrlEditorContent.length > 0 || dropdownText.includes(testFunctionName);
|
|
expect(hasVrlContent, 'Bug #9690: VRL function should load in saved view').toBeTruthy();
|
|
testLogger.info('✓ PRIMARY CHECK PASSED: VRL function loaded in saved view');
|
|
|
|
} catch (error) {
|
|
testLogger.error(`Test error: ${error.message}`);
|
|
throw error;
|
|
} finally {
|
|
// Cleanup: Delete the saved view if possible
|
|
try {
|
|
await pm.logsPage.clickDeleteSavedViewButton(testViewName).catch(() => {});
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.clickConfirmButton().catch(() => {});
|
|
testLogger.info(`Cleaned up test view: ${testViewName}`);
|
|
} catch (cleanupError) {
|
|
testLogger.debug('Cleanup skipped or failed gracefully');
|
|
}
|
|
}
|
|
|
|
testLogger.info('✓ PASSED: VRL saved views test completed (Bug #9690)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #8224: Logs page showing message not display
|
|
// https://github.com/openobserve/openobserve/issues/8224
|
|
// ==========================================================================
|
|
test("should display showing message on logs page after query @bug-8224 @P2 @showingMessage @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify showing message visible after query (Bug #8224)');
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Run query
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// STRONG ASSERTION: Result pagination (showing X of Y records) should be visible
|
|
await pm.logsPage.expectResultPaginationVisible();
|
|
testLogger.info('✓ Showing message / result pagination is visible');
|
|
|
|
// STRONG ASSERTION: Logs table should be visible (confirms data loaded)
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
testLogger.info('✓ Logs table visible with data');
|
|
|
|
testLogger.info('✓ PASSED: Showing message displays correctly (Bug #8224)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #5894: Timestamp should be default selected for multi stream selection
|
|
// https://github.com/openobserve/openobserve/issues/5894
|
|
// ==========================================================================
|
|
test("should have timestamp column selected by default for multi stream @bug-5894 @P2 @timestamp @multiStream @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify timestamp default selected for multi stream (Bug #5894)');
|
|
|
|
// The stream select's options are fetched when the logs page loads, so the second
|
|
// stream must exist before that navigation or it never appears in the dropdown.
|
|
const secondStream = `e2e_multistream_${Date.now()}`;
|
|
fieldCacheStreamsToCleanup.push(secondStream);
|
|
testLogger.info(`Ingesting test data into second stream: ${secondStream}`);
|
|
await ingestTestData(page, secondStream);
|
|
await pm.logsPage.waitForStreamAvailable(secondStream, 90000, 3000);
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
testLogger.info('Selecting second stream for multi-stream mode');
|
|
await pm.logsPage.addStreamToSelection(secondStream);
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Run query
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// STRONG ASSERTION: _timestamp column should be visible in the table header
|
|
await pm.logsPage.expectTimestampColumnVisible();
|
|
testLogger.info('✓ _timestamp column visible in table');
|
|
|
|
testLogger.info('✓ PASSED: Timestamp column displayed for multi stream (Bug #5894)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #10344: Run query button infinite loading when switching home↔logs
|
|
// https://github.com/openobserve/openobserve/issues/10344
|
|
// ==========================================================================
|
|
test("should not have run query button stuck in loading state after home-logs navigation @bug-10344 @P0 @runQuery @loading @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify run query button not stuck loading after navigation (Bug #10344)');
|
|
|
|
// Navigate to logs page via SPA menu click (not page.goto — Bug #10344 is about
|
|
// SPA state-leak across in-app navigation, which full reloads would reset)
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Navigate between home and logs multiple times using SPA menu clicks
|
|
for (let i = 0; i < 4; i++) {
|
|
testLogger.info(`Navigation cycle ${i + 1}/4: Logs → Home → Logs`);
|
|
|
|
// Verify run query button is visible and not in loading state on logs page
|
|
await pm.logsPage.expectRefreshButtonVisible();
|
|
await pm.logsPage.expectRefreshButtonEnabled();
|
|
testLogger.info(`✓ Run query button not loading on logs page (cycle ${i + 1})`);
|
|
|
|
// Navigate to home page via SPA menu click
|
|
await pm.logsPage.navigateToHome();
|
|
await page.waitForTimeout(1000);
|
|
testLogger.info(`✓ Home page loaded via SPA (cycle ${i + 1})`);
|
|
|
|
// Navigate back to logs page via SPA menu click
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForTimeout(1000);
|
|
}
|
|
|
|
// STRONG ASSERTION: After all navigation cycles, button should still be enabled
|
|
await pm.logsPage.expectRefreshButtonEnabled();
|
|
testLogger.info('✓ Run query button enabled after all SPA navigation cycles');
|
|
|
|
testLogger.info('✓ PASSED: Run query button not stuck in loading state (Bug #10344)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #11469: Copy, include exclude displayed on hover under traces correlation
|
|
// https://github.com/openobserve/openobserve/issues/11469
|
|
// ==========================================================================
|
|
test("should not show copy/include/exclude on hover in traces correlation area @bug-11469 @P2 @correlation @hover @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify correlation section hover behavior (Bug #11469)');
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Run query to load data (traces was ingested by global setup)
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Expand a log row to reveal the detail dialog with correlation tabs
|
|
await pm.logsPage.clickTableExpandMenuFirst();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Bug #11469 is about the traces correlation panel — an enterprise feature.
|
|
// If View Related is not available (OSS), skip rather than testing an unrelated area.
|
|
const correlationAvailable = await pm.logsPage.isViewRelatedButtonVisible();
|
|
if (!correlationAvailable) {
|
|
test.skip(true, 'Bug #11469: View Related (correlation) not available — enterprise feature required');
|
|
return;
|
|
}
|
|
|
|
await pm.logsPage.clickViewRelatedButton();
|
|
await page.waitForTimeout(2000);
|
|
testLogger.info('Opened correlation / View Related panel');
|
|
|
|
// Bug assertion: hover over the correlation panel and verify no
|
|
// copy/include/exclude context menu appears on simple hover
|
|
await pm.logsPage.hoverOnCorrelationDashboard();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.expectNoContextMenuVisible();
|
|
testLogger.info('✓ No unwanted context menu on hover over correlation panel');
|
|
|
|
testLogger.info('✓ PASSED: Correlation hover behavior verified (Bug #11469)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #5010: Console error when aggregating field with non-existent alias
|
|
// https://github.com/openobserve/openobserve/issues/5010
|
|
// ==========================================================================
|
|
test("should not have console errors when aggregating with non-existent alias @bug-5010 @P1 @errorHandling @consoleError @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify no console errors with non-existent alias aggregation (Bug #5010)');
|
|
|
|
// Track console errors
|
|
const consoleErrors = [];
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
consoleErrors.push(msg.text());
|
|
testLogger.warn(`Console error: ${msg.text()}`);
|
|
}
|
|
});
|
|
page.on('pageerror', err => {
|
|
consoleErrors.push(err.message);
|
|
testLogger.warn(`Page error: ${err.message}`);
|
|
});
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Enable SQL mode to write aggregation query
|
|
await pm.logsPage.enableSqlModeIfNeeded();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Write aggregation query with non-existent alias
|
|
const nonExistentAlias = `nonexistent_alias_${Date.now()}`;
|
|
const aggregationQuery = `SELECT count(*) as ${nonExistentAlias} FROM "e2e_automate"`;
|
|
testLogger.info(`Running aggregation query with non-existent alias: ${aggregationQuery}`);
|
|
await pm.logsPage.clearAndFillQueryEditor(aggregationQuery);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Run query
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Check for query-level errors
|
|
const errorVisible = await pm.logsPage.getErrorMessageLocator().isVisible({ timeout: 3000 }).catch(() => false);
|
|
testLogger.info(`Query error message visible: ${errorVisible}`);
|
|
|
|
// Expand first log detail if available
|
|
const expandMenu = pm.logsPage.getFirstRowExpandMenu();
|
|
if (await expandMenu.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await pm.logsPage.clickTableExpandMenuFirst();
|
|
await page.waitForTimeout(1000);
|
|
testLogger.info('Expanded first log detail');
|
|
}
|
|
|
|
// Wait for any delayed console errors
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Filter to only errors directly related to the bug: the specific non-existent
|
|
// alias we created, or "Cannot read properties of undefined" (the bug signature)
|
|
const relevantErrors = consoleErrors.filter(e =>
|
|
e.includes(nonExistentAlias) || /Cannot read properties of undefined/.test(e)
|
|
);
|
|
expect(relevantErrors.length, `Bug #5010: Should have no console errors related to non-existent alias, got: ${JSON.stringify(relevantErrors)}`).toBe(0);
|
|
testLogger.info('✓ No relevant console errors detected (filtered from ' + consoleErrors.length + ' total)');
|
|
|
|
testLogger.info('✓ PASSED: No console errors with non-existent alias (Bug #5010)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #5278: Not getting pagination when histogram is turned off
|
|
// https://github.com/openobserve/openobserve/issues/5278
|
|
// ==========================================================================
|
|
test("should show correct pagination when histogram is turned off @bug-5278 @P1 @pagination @histogram @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify pagination shows correctly with histogram off (Bug #5278)');
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Turn off histogram
|
|
await pm.logsPage.ensureHistogramToggleState(false);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Set wide time range to get more records
|
|
await pm.logsPage.clickDateTimeButton();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.clickRelative6WeeksButton();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Run query
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
await page.waitForTimeout(3000);
|
|
|
|
// STRONG ASSERTION: Logs table should be visible (query completed)
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
testLogger.info('✓ Logs table visible');
|
|
|
|
// STRONG ASSERTION: Result pagination should be visible
|
|
await pm.logsPage.expectResultPaginationVisible();
|
|
testLogger.info('✓ Result pagination visible with histogram off');
|
|
|
|
// STRONG ASSERTION: SQL pagination should NOT be visible when SQL mode is off
|
|
await pm.logsPage.expectSQLPaginationNotVisible();
|
|
testLogger.info('✓ SQL pagination NOT visible (expected with SQL mode off)');
|
|
|
|
testLogger.info('✓ PASSED: Pagination displayed correctly with histogram off (Bug #5278)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #3821: Cannot read properties of undefined (reading 'partitions')
|
|
// when user changes stream selection during query
|
|
// https://github.com/openobserve/openobserve/issues/3821
|
|
// ==========================================================================
|
|
test("should not show partitions error when changing stream during query @bug-3821 @P1 @errorHandling @streamSwitch @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify no partitions error on stream change during query (Bug #3821)');
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Set wide time range to make query take longer
|
|
await pm.logsPage.clickDateTimeButton();
|
|
await page.waitForTimeout(300);
|
|
await pm.logsPage.clickRelative6WeeksButton();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Track console errors
|
|
const consoleErrors = [];
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
consoleErrors.push(msg.text());
|
|
testLogger.warn(`Console error: ${msg.text()}`);
|
|
}
|
|
});
|
|
page.on('pageerror', err => {
|
|
consoleErrors.push(err.message);
|
|
testLogger.warn(`Page error: ${err.message}`);
|
|
});
|
|
|
|
// Start running the query
|
|
await pm.logsPage.clickRefreshButton();
|
|
// Don't wait for query to finish - switch streams while it's in progress
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Deselect the stream while query is in progress
|
|
testLogger.info('Deselecting stream while query in progress');
|
|
await pm.logsPage.deselectStream('e2e_automate');
|
|
|
|
// Wait for page to settle
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// STRONG ASSERTION: No "Cannot read properties of undefined" or "partitions" errors
|
|
const partitionsErrors = consoleErrors.filter(e =>
|
|
e.includes('Cannot read properties of undefined') || e.includes('partitions')
|
|
);
|
|
expect(partitionsErrors.length, `Bug #3821: Should have no partitions errors, got: ${JSON.stringify(partitionsErrors)}`).toBe(0);
|
|
testLogger.info('✓ No partitions-related console errors');
|
|
|
|
// Verify no error message displayed on page
|
|
const errorMsg = pm.logsPage.getErrorMessageLocator();
|
|
const errorVisible = await errorMsg.isVisible({ timeout: 2000 }).catch(() => false);
|
|
if (errorVisible) {
|
|
const errorText = await errorMsg.textContent();
|
|
const hasPartitionsError = /partitions|cannot read properties/i.test(errorText);
|
|
expect(hasPartitionsError, `Bug #3821: Error message should not mention partitions. Got: ${errorText}`).toBe(false);
|
|
}
|
|
testLogger.info('✓ No partitions error message on page');
|
|
|
|
testLogger.info('✓ PASSED: No partitions error on stream change (Bug #3821)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #6702: Cannot read properties of undefined partitions error when
|
|
// switching from logs to streams and back during query
|
|
// https://github.com/openobserve/openobserve/issues/6702
|
|
// ==========================================================================
|
|
test("should not show partitions error when switching pages during query @bug-6702 @P1 @errorHandling @navigation @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify no partitions error on page switch during query (Bug #6702)');
|
|
|
|
// Use SPA menu navigation — not page.goto — because full reloads reset Pinia/Vue state,
|
|
// destroying the in-flight query that the bug requires to trigger the partitions error
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Set wide time range
|
|
await pm.logsPage.clickDateTimeButton();
|
|
await page.waitForTimeout(300);
|
|
await pm.logsPage.clickRelative6WeeksButton();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Track page errors
|
|
const pageErrors = [];
|
|
page.on('pageerror', err => {
|
|
pageErrors.push(err.message);
|
|
testLogger.warn(`Page error: ${err.message}`);
|
|
});
|
|
|
|
// Start query
|
|
await pm.logsPage.clickRefreshButton();
|
|
// Don't wait for completion - switch pages while query is in progress
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Navigate to streams page via SPA while query is still running
|
|
testLogger.info('Switching to streams page during query (SPA)');
|
|
await pm.logsPage.clickMenuLinkStreamsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Navigate back to logs page via SPA
|
|
testLogger.info('Switching back to logs page (SPA)');
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(3000);
|
|
|
|
// STRONG ASSERTION: No "Cannot read properties of undefined" or "partitions" errors
|
|
const partitionsErrors = pageErrors.filter(e =>
|
|
e.includes('Cannot read properties of undefined') || e.includes('partitions')
|
|
);
|
|
expect(partitionsErrors.length, `Bug #6702: Should have no partitions page errors, got: ${JSON.stringify(partitionsErrors)}`).toBe(0);
|
|
testLogger.info('✓ No partitions-related page errors');
|
|
|
|
testLogger.info('✓ PASSED: No partitions error on page switch (Bug #6702)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #3131: Order by not displayed by default for distinct queries and
|
|
// interesting field does not disappear on deselecting
|
|
// https://github.com/openobserve/openobserve/issues/3131
|
|
// ==========================================================================
|
|
// SKIPPED: ORDER BY auto-injection for DISTINCT queries is server-side behavior
|
|
// (Rust backend). The frontend sends the exact SQL as typed — neither the Monaco
|
|
// editor text nor the _search POST payload will ever contain an auto-injected
|
|
// ORDER BY clause. There is no UI-observable difference between the fixed and
|
|
// broken states, so a meaningful UI regression test is not feasible for this bug.
|
|
test.skip("should show order by default for distinct queries @bug-3131 @P2 @distinct @orderBy @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify order by displayed for distinct queries (Bug #3131) — SKIPPED: backend behavior, not UI-verifiable');
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
await pm.logsPage.enableSqlModeIfNeeded();
|
|
await page.waitForTimeout(500);
|
|
|
|
const distinctQuery = `SELECT DISTINCT kubernetes_pod_name FROM "e2e_automate"`;
|
|
await pm.logsPage.clearAndFillQueryEditor(distinctQuery);
|
|
await page.waitForTimeout(500);
|
|
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
await pm.logsPage.expectResultPaginationVisible();
|
|
|
|
testLogger.info('✓ PASSED: Order by default for distinct queries verified (Bug #3131)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #4315: Blank histogram on cancelling histogram API/query
|
|
// https://github.com/openobserve/openobserve/issues/4315
|
|
// ==========================================================================
|
|
test("should not show blank histogram after cancelling query @bug-4315 @P2 @histogram @cancel @regression", async ({ page }) => {
|
|
testLogger.info('Test: Verify histogram not blank after cancel (Bug #4315)');
|
|
|
|
const orgName = getOrgIdentifier() || 'default';
|
|
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Ensure histogram is enabled
|
|
await pm.logsPage.enableHistogram();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Intercept _search POST requests to simulate a slow histogram call that gets cancelled.
|
|
// Use a wasCancelled flag + Promise.race with a guard timeout so the test cannot hang
|
|
// forever if clickRefreshButton() never fires a _search POST (slow CI, network warm-up).
|
|
let heldRoute = null;
|
|
let routeResolve = null;
|
|
let wasCancelled = false;
|
|
await page.route(`**/api/${orgName}/_search`, async (route) => {
|
|
if (route.request().method() === 'POST') {
|
|
if (!heldRoute) {
|
|
heldRoute = route;
|
|
testLogger.info('Holding first _search request (simulating slow histogram)');
|
|
// Timeout guard: if cancel never fires, continue the request after 8s instead of hanging
|
|
await Promise.race([
|
|
new Promise(resolve => { routeResolve = resolve; }),
|
|
new Promise(resolve => setTimeout(resolve, 8000))
|
|
]);
|
|
if (wasCancelled) {
|
|
testLogger.info('First _search request released (aborted by cancel)');
|
|
await route.abort('aborted');
|
|
} else {
|
|
testLogger.info('Cancel guard timed out — continuing request normally');
|
|
await route.continue();
|
|
}
|
|
heldRoute = null;
|
|
routeResolve = null;
|
|
wasCancelled = false;
|
|
return;
|
|
}
|
|
}
|
|
await route.continue();
|
|
});
|
|
|
|
// Run query — first _search request will be held
|
|
testLogger.info('Starting query (histogram request will be delayed)');
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Cancel by clicking refresh again while the first histogram call is still in flight.
|
|
// wasCancelled tells the route handler to abort rather than continue.
|
|
testLogger.info('Cancelling in-flight histogram by clicking refresh again');
|
|
wasCancelled = true;
|
|
if (routeResolve) routeResolve();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Remove route handler
|
|
await page.unroute(`**/api/${orgName}/_search`);
|
|
|
|
// Bug assertion: the histogram must NOT be blank after cancel — it must have rendered content
|
|
await pm.logsPage.expectBarChartHasContent();
|
|
|
|
testLogger.info('✓ PASSED: Histogram not blank after cancel (Bug #4315)');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #10103: Query execution plan open/close shows "No results found"
|
|
// https://github.com/openobserve/openobserve/issues/10103
|
|
// ==========================================================================
|
|
test("Opening and closing query execution plan should not clear results @bug-10103 @P2 @regression @logsRegression", async ({ page }) => {
|
|
testLogger.info('Test: Verify Explain Query close does not break results (Bug #10103)');
|
|
|
|
await pm.logsPage.navigateToLogs();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(2000);
|
|
|
|
await pm.logsPage.enableSqlModeIfNeeded();
|
|
await page.waitForTimeout(500);
|
|
|
|
await pm.logsPage.clickRunQueryButton();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Verify results visible before opening explain
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
testLogger.info('Results visible before explain');
|
|
|
|
// Open hamburger menu → click Explain Query via POM
|
|
await expect(pm.logsPage.getMoreOptionsButtonLocator(), 'More options button should be visible')
|
|
.toBeVisible({ timeout: 3000 });
|
|
await pm.logsPage.clickMoreOptionsButton();
|
|
await page.waitForTimeout(1000);
|
|
|
|
await expect(pm.logsPage.getExplainQueryMenuBtnLocator(),
|
|
'Explain Query option should be visible').toBeVisible({ timeout: 3000 });
|
|
await pm.logsPage.clickExplainQuery();
|
|
// Without this the test passes even when the dialog never opens.
|
|
await pm.logsPage.expectQueryPlanDialogVisible();
|
|
|
|
await page.keyboard.press('Escape');
|
|
await pm.logsPage.expectQueryPlanDialogHidden();
|
|
|
|
await pm.logsPage.expectResultsGridSettledWithRows();
|
|
testLogger.info('PASSED: Explain Query close does not break results');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #8641: Non-SQL mode multi-stream query not working
|
|
// https://github.com/openobserve/openobserve/issues/8641
|
|
// ==========================================================================
|
|
test("Non-SQL mode should not error when querying multiple streams @bug-8641 @P2 @regression @logsRegression", async ({ page }) => {
|
|
testLogger.info('Test: Verify non-SQL multi-stream query works (Bug #8641)');
|
|
|
|
// Ingest data into a second stream so the multi-stream join has two streams
|
|
const orgId = getOrgIdentifier() || 'default';
|
|
const headers = getHeaders();
|
|
// Per-run unique name — the fixed 'e2e_8641_stream' collided with concurrent shared-org runs
|
|
// ("Stream not available. Ingestion may have failed."). Keep the e2e_ prefix for cleanup.
|
|
const secondStream = 'e2e_8641_stream_' + Math.random().toString(36).slice(2, 7);
|
|
await sendRequest(page, getIngestionUrl(orgId, secondStream), [{
|
|
level: 'info', job: 'test_8641', log: 'test message for multi-stream', e2e: '1',
|
|
}], headers);
|
|
testLogger.info(`Ingested data to stream: ${secondStream}`);
|
|
// Register for cleanup in afterEach
|
|
fieldCacheStreamsToCleanup.push(secondStream);
|
|
|
|
// selectIndexAndStreamJoinUnion handles its own navigation to logs
|
|
await pm.logsPage.selectIndexAndStreamJoinUnion(secondStream, 'e2e_automate');
|
|
testLogger.info('Multi-stream join selected');
|
|
|
|
// Run query and verify no error
|
|
await pm.logsPage.clickRunQueryButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
const errorVisible = await pm.logsPage.getGenericErrorLocator()
|
|
.filter({ hasText: /error|failed|invalid/i })
|
|
.first().isVisible({ timeout: 2000 }).catch(() => false);
|
|
|
|
expect(errorVisible,
|
|
'Bug #8641: Non-SQL multi-stream query should not display an error'
|
|
).toBeFalsy();
|
|
|
|
testLogger.info('PASSED: Non-SQL multi-stream query verified');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #5839: Logs page searchbar UI issue when sharing
|
|
// https://github.com/openobserve/openobserve/issues/5839
|
|
// ==========================================================================
|
|
test("Shared logs page should display searchbar correctly @bug-5839 @P3 @regression @logsRegression", async ({ page }) => {
|
|
testLogger.info('Test: Verify shared logs page searchbar renders correctly (Bug #5839)');
|
|
|
|
// Navigate using the sidebar (same pattern as other passing logs tests)
|
|
// then select stream without re-navigating (skipNavigation=true).
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
// Wait for the stream select OSelect to mount before interacting
|
|
await pm.logsPage.getStreamDropdown().first().waitFor({ state: 'visible', timeout: 10000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate', 5, 30000, true);
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Run a query so the page has results state
|
|
await pm.logsPage.clickRunQueryButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Open share via POM and verify success notification (link copied to clipboard)
|
|
const shareInteractionWorked = await pm.logsPage.clickShareLinkAndExpectSuccess();
|
|
testLogger.info(`Share notification visible: ${shareInteractionWorked}`);
|
|
expect(shareInteractionWorked,
|
|
'Bug #5839: Share action should produce a success notification (link copied)'
|
|
).toBeTruthy();
|
|
|
|
// Bug #5839: share interaction must not fail and the current page searchbar
|
|
// must remain visible and functional after the share action
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
testLogger.info('PASSED: Shared logs page searchbar verified');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #10821: Quick mode is not working when clicking on text,
|
|
// but histogram toggle works
|
|
// https://github.com/openobserve/openobserve/issues/10821
|
|
// ==========================================================================
|
|
test("Quick mode should activate when clicking its toggle (not just histogram toggle)", {
|
|
tag: ['@bug-10821', '@P1', '@regression', '@logsRegression', '@logsRegressionQuickMode']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Verify quick mode toggle activates on click (Bug #10821)');
|
|
|
|
// Navigate to logs page
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await pm.logsPage.selectStream("e2e_automate");
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Run query first so search bar controls are fully rendered
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
|
|
// Use page object to find the quick/SQL mode toggle
|
|
const quickModeToggle = await pm.logsPage.findQueryModeToggle();
|
|
|
|
// Bug #10821 is about this toggle not responding to clicks —
|
|
// if the toggle doesn't exist at all, the bug check is inconclusive.
|
|
if (!quickModeToggle) {
|
|
testLogger.warn('Quick mode toggle not found with any known selector');
|
|
test.skip(true, 'Quick mode toggle selector not available — cannot verify bug #10821');
|
|
return;
|
|
}
|
|
|
|
// Capture pre-click state to verify the toggle actually changed
|
|
const isActiveBefore = await pm.logsPage.isToggleActive(quickModeToggle);
|
|
testLogger.info(`Mode toggle state before click: ${isActiveBefore}`);
|
|
|
|
// Click the toggle
|
|
await quickModeToggle.click();
|
|
await page.waitForTimeout(1000);
|
|
testLogger.info('✓ Clicked quick/SQL mode toggle');
|
|
|
|
const isActiveAfter = await pm.logsPage.isToggleActive(quickModeToggle);
|
|
testLogger.info(`Mode toggle state after click: ${isActiveAfter}`);
|
|
|
|
// Bug #10821: the toggle should change state on click
|
|
expect(isActiveBefore !== null && isActiveAfter !== null && isActiveBefore !== isActiveAfter,
|
|
'Bug #10821: Quick mode toggle should change state on click'
|
|
).toBeTruthy();
|
|
|
|
// Verify histogram toggle also works independently
|
|
const histogramToggleVisible = await pm.logsPage.isHistogramToggleVisible();
|
|
if (histogramToggleVisible) {
|
|
await pm.logsPage.clickHistogramToggle();
|
|
testLogger.info('✓ Clicked histogram toggle');
|
|
}
|
|
|
|
testLogger.info('✓ PASSED: Mode toggle interaction verified');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #10595: ExpandedRow visible in 2 rows when user opens first row
|
|
// and clicks on run query
|
|
// https://github.com/openobserve/openobserve/issues/10595
|
|
// ==========================================================================
|
|
test("Only one row should be expanded after clicking run query", {
|
|
tag: ['@bug-10595', '@P1', '@regression', '@logsRegression', '@logsRegressionTableRowExpand']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Verify single row expansion after run query (Bug #10595)');
|
|
|
|
// Navigate to logs and load data
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await pm.logsPage.selectStream("e2e_automate");
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// Run query to get results
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
// Check for table rows using page object
|
|
const tableRows = pm.logsPage.getLogsTableRows();
|
|
const rowCount = await tableRows.count();
|
|
testLogger.info(`Table row count: ${rowCount}`);
|
|
|
|
if (rowCount < 2) {
|
|
testLogger.warn('Not enough rows to test expansion — skipping');
|
|
test.skip(true, 'Need at least 2 rows in the table');
|
|
return;
|
|
}
|
|
|
|
// Expand the first row using page object
|
|
const firstRowExpand = pm.logsPage.getFirstRowExpandMenu();
|
|
if (await firstRowExpand.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await firstRowExpand.click();
|
|
testLogger.info('✓ Clicked expand menu on first row');
|
|
await page.waitForTimeout(1000);
|
|
// The expand menu opens a dropdown — click it again to toggle the row expansion
|
|
const expandButton = pm.logsPage.getFirstRowExpandMenu();
|
|
if (await expandButton.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await expandButton.click();
|
|
await page.waitForTimeout(500);
|
|
}
|
|
} else {
|
|
await tableRows.first().click();
|
|
testLogger.info('✓ Clicked first row directly');
|
|
await page.waitForTimeout(1000);
|
|
}
|
|
|
|
// Now click run query
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
// PRIMARY ASSERTION: Bug #10595 caused 2+ rows to expand after re-run.
|
|
const tableBody = pm.logsPage.getLogsTableBody();
|
|
const expandedCount = await tableBody.locator('[aria-expanded="true"]').count();
|
|
testLogger.info(`Expanded table rows after run query: ${expandedCount}`);
|
|
|
|
expect(expandedCount,
|
|
'Bug #10595: At most 1 row should be expanded after re-run (bug caused 2+ rows to expand)'
|
|
).toBeLessThanOrEqual(1);
|
|
|
|
testLogger.info('✓ PASSED: Single row expansion verified after run query');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #9049: Decimal Values should not be accepted in the datetime picker
|
|
// https://github.com/openobserve/openobserve/issues/9049
|
|
// ==========================================================================
|
|
test("Decimal values should be rejected in the absolute datetime picker", {
|
|
tag: ['@bug-9049', '@P1', '@regression', '@logsRegression', '@logsRegressionDatetimeValidation']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Verify decimal values rejected in datetime picker (Bug #9049)');
|
|
|
|
// Navigate to logs page
|
|
await pm.logsPage.clickMenuLinkLogsItem();
|
|
await pm.logsPage.selectStream("e2e_automate");
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// Open the date-time picker
|
|
await pm.logsPage.clickDateTimeButton();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Switch to absolute time tab
|
|
await pm.logsPage.clickAbsoluteTimeTab();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Find the time input using page object
|
|
const timeInput = await pm.logsPage.findTimeInput();
|
|
if (timeInput) {
|
|
testLogger.info('Found time input via page object');
|
|
}
|
|
|
|
if (timeInput) {
|
|
// Clear existing value and type a decimal.
|
|
// Use pressSequentially to bypass native input[type=time] validation that
|
|
// rejects "12.5:00" at the fill() level.
|
|
await timeInput.click();
|
|
await timeInput.fill('');
|
|
await timeInput.pressSequentially('12.5:00');
|
|
await page.waitForTimeout(500);
|
|
|
|
// Click outside to trigger validation
|
|
await pm.logsPage.getDateTimeButton().click().catch(() => {});
|
|
await page.waitForTimeout(500);
|
|
|
|
// Get the value to check if decimal was accepted
|
|
const inputValue = await timeInput.inputValue().catch(() => '');
|
|
testLogger.info(`Time input value after decimal entry: "${inputValue}"`);
|
|
|
|
// PRIMARY ASSERTION: Decimal value should be rejected or corrected
|
|
const hasDecimalInValue = inputValue.includes('.');
|
|
const errorVisible = await pm.logsPage.getLogsSearchErrorMessage()
|
|
.isVisible({ timeout: 2000 }).catch(() => false);
|
|
|
|
testLogger.info(`Decimal in value: ${hasDecimalInValue}, Error visible: ${errorVisible}`);
|
|
|
|
// The bug is fixed if either: the decimal is stripped, or an error is shown
|
|
expect(hasDecimalInValue && !errorVisible,
|
|
'Bug #9049: Decimal value should not be accepted in datetime time input without error'
|
|
).toBeFalsy();
|
|
|
|
testLogger.info('✓ PASSED: Decimal value rejected in datetime picker');
|
|
} else {
|
|
// Time input not in absolute tab — check if the time is entered via time picker buttons instead
|
|
// Uses class-based selector as fallback (the QTime component has no data-test attr)
|
|
const timePickerVisible = await pm.logsPage.getTimePicker()
|
|
.isVisible({ timeout: 2000 }).catch(() => false);
|
|
|
|
if (timePickerVisible) {
|
|
testLogger.info('Time picker component found — decimal entry not possible via button UI');
|
|
testLogger.info('✓ PASSED: Time picker uses button UI — decimal entry is naturally prevented');
|
|
} else {
|
|
testLogger.warn('Time input not found — skipping');
|
|
test.skip(true, 'Time input element not available in current UI');
|
|
}
|
|
}
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #11606: include/exclude search term gets added at incorrect position
|
|
// https://github.com/openobserve/openobserve/issues/11606
|
|
// ==========================================================================
|
|
test("include/exclude search term added at correct position in query", {
|
|
tag: ['@bug-11606', '@P1', '@regression', '@logsRegression']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: include/exclude search term position (Bug #11606)');
|
|
|
|
const orgName = getOrgIdentifier() || 'default';
|
|
await page.goto(`/web/logs?org_identifier=${orgName}&stream=e2e_automate&stream_type=logs`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
await pm.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(3000);
|
|
|
|
const logRow = pm.logsPage.getLogsTableRows().first();
|
|
if (await logRow.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await logRow.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
const includeExcludeVisible = await pm.logsPage.expectIncludeExcludeButtonsVisibleInLogDetails()
|
|
.then(() => true).catch(() => false);
|
|
|
|
if (includeExcludeVisible) {
|
|
testLogger.info('Include/Exclude buttons are visible in log details');
|
|
try {
|
|
await pm.logsPage.clickIncludeFieldButton();
|
|
await page.waitForTimeout(500);
|
|
testLogger.info('Include field action completed');
|
|
} catch (err) {
|
|
testLogger.warn(`Include field button not interactable: ${err.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PRIMARY ASSERTION: Query editor must remain visible and functional after include action
|
|
const queryEditor = pm.logsPage.getQueryEditor();
|
|
await expect(queryEditor, 'Bug #11606: Query editor must remain visible after include action').toBeVisible({ timeout: 5000 });
|
|
|
|
testLogger.info('Bug #11606 verification complete');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #9339: collapse or expand the indexlist wont redraw the histogram chart
|
|
// https://github.com/openobserve/openobserve/issues/9339
|
|
// ==========================================================================
|
|
test("collapsing/expanding index list should redraw histogram chart", {
|
|
tag: ['@bug-9339', '@P1', '@regression', '@logsRegression']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Histogram redraws on index list collapse/expand (Bug #9339)');
|
|
|
|
const orgName = getOrgIdentifier() || 'default';
|
|
await page.goto(`/web/logs?org_identifier=${orgName}&stream=e2e_automate&stream_type=logs`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
await pm.logsPage.ensureHistogramState(true);
|
|
await pm.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(3000);
|
|
|
|
await pm.logsPage.expectBarChartHasContent();
|
|
testLogger.info('Histogram chart is visible');
|
|
|
|
await pm.logsPage.clickFieldListCollapseButton();
|
|
await page.waitForTimeout(1000);
|
|
await pm.logsPage.clickFieldListCollapseButton();
|
|
await page.waitForTimeout(1000);
|
|
|
|
const barChart = pm.logsPage.getBarChart();
|
|
await expect(barChart).toBeVisible({ timeout: 5000 });
|
|
testLogger.info('Histogram chart still visible after index list collapse/expand');
|
|
|
|
testLogger.info('Bug #9339 verification complete');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #7310: Search term appended instead of replaced in streams dropdown
|
|
// https://github.com/openobserve/openobserve/issues/7310
|
|
// ==========================================================================
|
|
test("stream dropdown selection should replace search term not append", {
|
|
tag: ['@bug-7310', '@P1', '@regression', '@logsRegression']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Stream dropdown replaces search term on selection (Bug #7310)');
|
|
|
|
const orgName = getOrgIdentifier() || 'default';
|
|
await page.goto(`/web/logs?org_identifier=${orgName}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
const streamDropdown = pm.logsPage.getStreamDropdown();
|
|
await streamDropdown.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const searchInput = pm.logsPage.getStreamSearchInput();
|
|
if (await searchInput.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await searchInput.fill('e2e');
|
|
await page.waitForTimeout(1000);
|
|
|
|
const inputValue = await searchInput.inputValue().catch(() => '');
|
|
testLogger.info(`Stream search input value: "${inputValue}"`);
|
|
expect(inputValue).not.toMatch(/e2ee2e/);
|
|
}
|
|
|
|
// UNCONDITIONAL: Stream dropdown must remain functional after search
|
|
await expect(streamDropdown, 'Bug #7310: Stream dropdown must remain visible').toBeVisible({ timeout: 5000 });
|
|
|
|
testLogger.info('Bug #7310 verification complete');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #5277: After moving column, click on query again and position changes back
|
|
// https://github.com/openobserve/openobserve/issues/5277
|
|
// #4483 is the same defect filed again; both numbers are tagged so a
|
|
// coverage audit keyed on either one finds this test.
|
|
// ==========================================================================
|
|
test("column positions should persist after re-running query", {
|
|
tag: ['@bug-5277', '@bug-4483', '@P2', '@regression', '@logsRegression']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Column positions persist after re-query (Bug #5277)');
|
|
|
|
const orgName = getOrgIdentifier() || 'default';
|
|
await page.goto(`/web/logs?org_identifier=${orgName}&stream=e2e_automate&stream_type=logs`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
await pm.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(3000);
|
|
|
|
const timestampHeader = pm.logsPage.getTimestampColumnHeader();
|
|
const sourceHeader = pm.logsPage.getSourceColumnHeader();
|
|
|
|
const timestampVisible = await timestampHeader.isVisible({ timeout: 5000 }).catch(() => false);
|
|
const sourceVisible = await sourceHeader.isVisible({ timeout: 5000 }).catch(() => false);
|
|
|
|
if (timestampVisible && sourceVisible) {
|
|
testLogger.info('Column headers are visible before re-query');
|
|
await pm.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(3000);
|
|
|
|
expect(await timestampHeader.isVisible({ timeout: 5000 }).catch(() => false)).toBe(true);
|
|
expect(await sourceHeader.isVisible({ timeout: 5000 }).catch(() => false)).toBe(true);
|
|
testLogger.info('Column positions persisted after re-running query');
|
|
}
|
|
|
|
// UNCONDITIONAL: Query editor must remain visible regardless of column state
|
|
const queryEditor = pm.logsPage.getQueryEditor();
|
|
await expect(queryEditor, 'Bug #5277: Query editor must remain visible').toBeVisible({ timeout: 5000 });
|
|
|
|
testLogger.info('Bug #5277 verification complete');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #4426: Text wrap in saved view causes misalignment on view switch
|
|
// https://github.com/openobserve/openobserve/issues/4426
|
|
// ==========================================================================
|
|
test("text wrap toggle should not cause column misalignment on saved view switch", {
|
|
tag: ['@bug-4426', '@P2', '@regression', '@logsRegression']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Text wrap toggle and saved view alignment (Bug #4426)');
|
|
|
|
const orgName = getOrgIdentifier() || 'default';
|
|
await page.goto(`/web/logs?org_identifier=${orgName}&stream=e2e_automate&stream_type=logs`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
await pm.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(3000);
|
|
|
|
const logRow = pm.logsPage.getLogsTableRows().first();
|
|
if (await logRow.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await logRow.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// The log detail sidebar opens on the JSON tab by default (Bug #9724); the wrap
|
|
// toggle only renders in the Table tab. Switch tabs before asserting its
|
|
// visibility — otherwise the check always waits out its full timeout on the
|
|
// hidden toggle and logs a misleading "Wrap toggle not found" warning.
|
|
try {
|
|
await pm.logsPage.clickLogDetailTableTab();
|
|
await pm.logsPage.verifyWrapToggleVisibleInTableTab();
|
|
testLogger.info('Wrap toggle is visible in log detail table tab');
|
|
} catch (err) {
|
|
testLogger.warn(`Wrap toggle not found in current log detail: ${err.message}`);
|
|
}
|
|
|
|
await page.keyboard.press('Escape');
|
|
await page.waitForTimeout(500);
|
|
|
|
const table = pm.logsPage.getLogsTable();
|
|
await expect(table).toBeVisible({ timeout: 5000 });
|
|
testLogger.info('Main table still visible and aligned after closing detail');
|
|
}
|
|
|
|
// UNCONDITIONAL: Logs result table must remain visible regardless of detail state
|
|
const mainTable = pm.logsPage.getLogsTable();
|
|
await expect(mainTable, 'Bug #4426: Logs result table must remain visible').toBeVisible({ timeout: 5000 });
|
|
|
|
testLogger.info('Bug #4426 verification complete');
|
|
});
|
|
|
|
// ==========================================================================
|
|
// Bug #4091: Logs-Visualise - Cancel query to work on visualize page
|
|
// https://github.com/openobserve/openobserve/issues/4091
|
|
// ==========================================================================
|
|
test("cancel query button should be available on visualize page", {
|
|
tag: ['@bug-4091', '@P1', '@regression', '@logsRegression']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Cancel query available on visualize page (Bug #4091)');
|
|
|
|
const orgName = getOrgIdentifier() || 'default';
|
|
await page.goto(`/web/logs?org_identifier=${orgName}&stream=e2e_automate&stream_type=logs`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
|
|
const visualizeToggle = pm.logsPage.getVisualizeToggle();
|
|
if (await visualizeToggle.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await visualizeToggle.click();
|
|
await page.waitForTimeout(1000);
|
|
testLogger.info('Navigated to visualize tab');
|
|
|
|
// Run query and verify visualize page responds
|
|
try {
|
|
await pm.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(500);
|
|
testLogger.info('Query executed on visualize tab');
|
|
} catch (err) {
|
|
testLogger.warn(`Query run on visualize tab not available: ${err.message}`);
|
|
}
|
|
|
|
// Verify visualize tab is still active (no crash/error)
|
|
await expect(visualizeToggle).toBeVisible({ timeout: 5000 });
|
|
testLogger.info('Visualize tab remains active');
|
|
}
|
|
|
|
// UNCONDITIONAL: Query editor must remain visible regardless of visualize toggle state
|
|
const queryEditor = pm.logsPage.getQueryEditor();
|
|
await expect(queryEditor, 'Bug #4091: Query editor must remain visible on logs page').toBeVisible({ timeout: 5000 });
|
|
|
|
testLogger.info('Bug #4091 verification complete');
|
|
});
|
|
|
|
|
|
// ==========================================================================
|
|
// Bug #14228: Builder saved view does not apply when already on the Build tab
|
|
// https://github.com/openobserve/openobserve/issues/14228
|
|
// ==========================================================================
|
|
// ==========================================================================
|
|
// Bug #13990: Builder saved view restores the wrong stream
|
|
// https://github.com/openobserve/openobserve/issues/13990
|
|
// ==========================================================================
|
|
test('Builder saved view restores the stream it was saved with @bug-13990 @P1 @regression @logsRegression @savedViews @queryBuilder', {
|
|
tag: ['@bug-13990', '@P1', '@regression', '@logsRegression', '@savedViews', '@queryBuilder']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Builder saved view restores its own stream (Bug #13990)');
|
|
|
|
const savedViewName = `streamslog_build_13990_${Date.now()}`;
|
|
const otherStream = 'e2e_13990_other_' + Math.random().toString(36).slice(2, 7);
|
|
|
|
// A second stream to move away to; without it the assertion would hold
|
|
// trivially, since the page would never have left the saved stream.
|
|
const orgId = getOrgIdentifier() || 'default';
|
|
await sendRequest(page, getIngestionUrl(orgId, otherStream), [{
|
|
level: 'info', job: 'test_13990', log: 'second stream for saved view restore', e2e: '1',
|
|
}], getHeaders());
|
|
fieldCacheStreamsToCleanup.push(otherStream);
|
|
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${orgId}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await pm.logsPage.expectLogsSearchIndexListContainsText('e2e_automate');
|
|
|
|
await createBuilderSavedView(pm, page, savedViewName, 'line');
|
|
|
|
await pm.logsPage.selectStream(otherStream);
|
|
await pm.logsPage.expectLogsSearchIndexListContainsText(otherStream);
|
|
testLogger.info(`Moved to ${otherStream} before applying the saved view`);
|
|
|
|
await applySavedViewByName(pm, page, savedViewName);
|
|
|
|
// The defect restored an unrelated stream, so the stream is the contract here
|
|
// rather than the chart type that #14228 covers.
|
|
await pm.logsPage.expectLogsSearchIndexListContainsText('e2e_automate');
|
|
testLogger.info('Saved view restored its original stream');
|
|
});
|
|
|
|
test("should apply a builder saved view while already on the build tab", {
|
|
tag: ['@bug-14228', '@P1', '@regression', '@logsRegression', '@savedViews', '@queryBuilder']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Builder saved view applies without a tab toggle (Bug #14228)');
|
|
test.setTimeout(180000);
|
|
|
|
// The "streamslog" prefix lets CI saved-view cleanup reap any leak.
|
|
const savedViewName = `streamslog_build_14228_${Date.now()}`;
|
|
|
|
try {
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
await createBuilderSavedView(pm, page, savedViewName, 'line');
|
|
|
|
// Diverge the live builder from the saved view. Without this the assertion
|
|
// would pass even unfixed, since the builder would already show "line".
|
|
await pm.logsPage.selectChartType('area');
|
|
await pm.logsPage.verifyChartTypeSelected('area');
|
|
await pm.logsPage.expectBuildTabSelected();
|
|
testLogger.info('Builder diverged to area chart before applying the saved view');
|
|
|
|
await applySavedViewByName(pm, page, savedViewName);
|
|
|
|
// No tab toggle happened, so BuildQueryPage never remounts — the watcher on
|
|
// searchObj.meta.savedBuildConfig is the only thing that can apply the view.
|
|
await pm.logsPage.expectBuildTabSelected();
|
|
await pm.logsPage.verifyChartTypeSelected('line');
|
|
await pm.logsPage.verifyChartTypeSelected('area', false);
|
|
testLogger.info('✓ PRIMARY CHECK PASSED: builder re-initialized to the saved chart type');
|
|
|
|
const xCount = await pm.logsPage.expectXAxisHasItems();
|
|
const yCount = await pm.logsPage.expectYAxisHasItems();
|
|
expect(xCount, 'Bug #14228: X-axis must stay populated after applying the view').toBeGreaterThan(0);
|
|
expect(yCount, 'Bug #14228: Y-axis must stay populated after applying the view').toBeGreaterThan(0);
|
|
|
|
} finally {
|
|
await deleteSavedViewQuietly(pm, page, savedViewName);
|
|
}
|
|
|
|
testLogger.info('✓ PASSED: Builder saved view applied in place (Bug #14228)');
|
|
});
|
|
|
|
test("should still apply a builder saved view when toggling in from the logs tab", {
|
|
tag: ['@bug-14228', '@P1', '@regression', '@logsRegression', '@savedViews', '@queryBuilder']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Toggle-in path still applies builder saved views (Bug #14228 no-regression)');
|
|
test.setTimeout(180000);
|
|
|
|
const savedViewName = `streamslog_build_14228_toggle_${Date.now()}`;
|
|
|
|
try {
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
await createBuilderSavedView(pm, page, savedViewName, 'line');
|
|
|
|
await pm.logsPage.clickLogsToggle();
|
|
await pm.logsPage.expectLogsTabSelected();
|
|
testLogger.info('Switched back to the Logs tab');
|
|
|
|
await applySavedViewByName(pm, page, savedViewName);
|
|
|
|
// Here the view flips the toggle to build and BuildQueryPage mounts, so
|
|
// onMounted applies the config. The watcher must not undo or double-apply it.
|
|
await pm.logsPage.expectBuildTabSelected();
|
|
await pm.logsPage.verifyChartTypeSelected('line');
|
|
testLogger.info('✓ CHECK PASSED: toggle-in path still restores the saved chart type');
|
|
|
|
} finally {
|
|
await deleteSavedViewQuietly(pm, page, savedViewName);
|
|
}
|
|
|
|
testLogger.info('✓ PASSED: Toggle-in path unaffected (Bug #14228)');
|
|
});
|
|
|
|
test("should not re-initialize the builder when applying a non-builder saved view", {
|
|
tag: ['@bug-14228', '@P2', '@regression', '@logsRegression', '@savedViews', '@queryBuilder']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Test: Non-builder saved views do not trigger builder re-init (Bug #14228)');
|
|
test.setTimeout(180000);
|
|
|
|
const savedViewName = `streamslog_logs_14228_${Date.now()}`;
|
|
|
|
try {
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier() || 'default'}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await pm.logsPage.selectStream('e2e_automate');
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Saved from the Logs tab, so the view carries no buildData.
|
|
await pm.logsPage.clickRefreshButton();
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
|
|
await pm.logsPage.clickSaveViewButton();
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.fillSavedViewName(savedViewName);
|
|
await page.waitForTimeout(500);
|
|
await pm.logsPage.clickSavedViewDialogSave();
|
|
await page.waitForTimeout(2000);
|
|
testLogger.info(`Logs-tab saved view created: ${savedViewName}`);
|
|
|
|
await pm.logsPage.clickBuildToggle();
|
|
await pm.logsPage.waitForBuildTabLoaded();
|
|
await pm.logsPage.expectBuildTabSelected();
|
|
|
|
await applySavedViewByName(pm, page, savedViewName);
|
|
|
|
// buildData is null, so the watcher's guard must skip it and the view's own
|
|
// logs toggle must win.
|
|
await pm.logsPage.expectLogsTabSelected();
|
|
await pm.logsPage.expectLogsTableVisible();
|
|
testLogger.info('✓ CHECK PASSED: non-builder view applied with no builder re-init');
|
|
|
|
} finally {
|
|
await deleteSavedViewQuietly(pm, page, savedViewName);
|
|
}
|
|
|
|
testLogger.info('✓ PASSED: Non-builder saved view handled correctly (Bug #14228)');
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
// Cleanup new stream created by field cache test (e2e_automate is never deleted)
|
|
if (fieldCacheStreamsToCleanup.length > 0 && pm) {
|
|
testLogger.info('Cleaning up test streams');
|
|
for (const streamName of fieldCacheStreamsToCleanup) {
|
|
await pm.logsPage.deleteStream(streamName).catch(e =>
|
|
testLogger.warn(`Failed to delete ${streamName}: ${e.message}`)
|
|
);
|
|
}
|
|
fieldCacheStreamsToCleanup = [];
|
|
testLogger.info('Test stream cleanup completed');
|
|
}
|
|
testLogger.info('Logs regression bug test completed');
|
|
});
|
|
});
|