mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
472 lines
20 KiB
JavaScript
472 lines
20 KiB
JavaScript
const { test, expect } = require('../utils/enhanced-baseFixtures.js');
|
|
const logData = require("../../fixtures/log.json");
|
|
const PageManager = require('../../pages/page-manager.js');
|
|
const { getHeaders, getIngestionUrl, sendRequest } = require('../../utils/apiUtils.js');
|
|
const testLogger = require('../utils/test-logger.js');
|
|
|
|
// Constants for consistent timeouts and test data
|
|
const MULTISTREAM_CONFIG = {
|
|
TIMEOUTS: {
|
|
STREAM_FILTER: 2000,
|
|
STREAM_SELECTION: 4000,
|
|
MULTISTREAM_SETUP: 5000,
|
|
QUERY_EXECUTION: 5000,
|
|
DATA_INDEXING: 3000,
|
|
UI_INTERACTION: 1000,
|
|
NAVIGATION: 2000,
|
|
TOAST_WAIT: 500
|
|
},
|
|
TEST_DATA: {
|
|
STREAMS: ['e2e_automate', 'e2e_stream1'],
|
|
QUERIES: {
|
|
INVALID_FIELD: 'random_test=2',
|
|
VRL_FUNCTION: '.a=2'
|
|
},
|
|
PAYLOADS: {
|
|
STREAM1: {
|
|
level: "debug",
|
|
job: "test_job_1",
|
|
log: "enhanced test message for stream 1",
|
|
e2e: "enhanced1",
|
|
stream_id: "e2e_automate"
|
|
},
|
|
STREAM2: {
|
|
level: "warning",
|
|
job: "test_job_2",
|
|
log: "enhanced test message for stream 2",
|
|
e2e: "enhanced2",
|
|
stream_id: "e2e_stream1"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Helper functions to reduce code duplication
|
|
async function verifyMultistreamSelection(page, streams = MULTISTREAM_CONFIG.TEST_DATA.STREAMS) {
|
|
const pageManager = new PageManager(page);
|
|
await pageManager.logsPage.expectLogsSearchIndexListContainsText(streams.join(', '));
|
|
}
|
|
|
|
async function setupBasicMultistream(page, streams = MULTISTREAM_CONFIG.TEST_DATA.STREAMS) {
|
|
const pageManager = new PageManager(page);
|
|
|
|
// Add second stream using POM
|
|
await pageManager.logsPage.fillStreamFilter(streams[1]);
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.STREAM_FILTER);
|
|
await pageManager.logsPage.toggleStreamSelection(streams[1]);
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.STREAM_SELECTION);
|
|
|
|
// Verify both streams are selected
|
|
await verifyMultistreamSelection(page, streams);
|
|
}
|
|
|
|
async function validateCombinedStreamResults(page, payloads) {
|
|
const pageManager = new PageManager(page);
|
|
const tableContent = await pageManager.logsPage.getLogsTableContent();
|
|
|
|
// Extract stream identifiers from payloads for validation
|
|
const stream1Indicators = [payloads.STREAM1.e2e, payloads.STREAM1.job, payloads.STREAM1.level];
|
|
const stream2Indicators = [payloads.STREAM2.e2e, payloads.STREAM2.job, payloads.STREAM2.level];
|
|
|
|
// Check for data from both streams
|
|
const hasStream1Data = stream1Indicators.some(indicator => tableContent.includes(indicator));
|
|
const hasStream2Data = stream2Indicators.some(indicator => tableContent.includes(indicator));
|
|
|
|
if (!hasStream1Data || !hasStream2Data) {
|
|
// Alternative check - verify we have multiple rows of data
|
|
const rowCount = await pageManager.logsPage.getLogsTableRowCount();
|
|
expect(rowCount).toBeGreaterThan(1);
|
|
testLogger.info('Multiple data rows found indicating combined results', { rowCount });
|
|
} else {
|
|
testLogger.info('Data from both streams verified in results');
|
|
}
|
|
}
|
|
|
|
test.describe.configure({ mode: "parallel" });
|
|
|
|
test.describe("Stream multiselect testcases", () => {
|
|
let pm;
|
|
|
|
function removeUTFCharacters(text) {
|
|
return text.replace(/[^\x00-\x7F]/g, " ");
|
|
}
|
|
|
|
async function applyQueryButton(page) {
|
|
const pm = new PageManager(page);
|
|
await pm.logsPage.applyQueryButton(logData.logsUrl);
|
|
}
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
pm = new PageManager(page);
|
|
|
|
const orgId = process.env["ORGNAME"];
|
|
const streamNames = ["e2e_automate", "e2e_stream1"];
|
|
const headers = getHeaders();
|
|
|
|
for (const streamName of streamNames) {
|
|
const ingestionUrl = getIngestionUrl(orgId, streamName);
|
|
const payload = {
|
|
level: "info",
|
|
job: "test",
|
|
log: "test message for openobserve",
|
|
e2e: "1",
|
|
};
|
|
const response = await sendRequest(page, ingestionUrl, payload, headers);
|
|
testLogger.debug('API response received', { streamName, response });
|
|
}
|
|
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${process.env["ORGNAME"]}`);
|
|
await pm.logsPage.selectStream("e2e_automate");
|
|
await applyQueryButton(page);
|
|
// Ensure quick mode is ON (idempotent) so the All Fields toggle is visible
|
|
await pm.logsPage.enableQuickModeIfDisabled();
|
|
await pm.logsPage.clickAllFieldsButton();
|
|
});
|
|
|
|
|
|
async function multistreamselect(page) {
|
|
const pageManager = new PageManager(page);
|
|
|
|
// Add second stream using POM (we're already on logs page from beforeEach with e2e_automate selected and all fields enabled)
|
|
await pageManager.logsPage.fillStreamFilter('e2e_stream1');
|
|
await page.waitForTimeout(2000);
|
|
await pageManager.logsPage.toggleStreamSelection('e2e_stream1');
|
|
await page.waitForTimeout(4000);
|
|
|
|
// Enable function editor using POM (all fields already clicked in beforeEach)
|
|
await pageManager.logsPage.toggleQueryModeEditor();
|
|
// Extra wait for Firefox to render the Monaco editor
|
|
await page.waitForTimeout(3000);
|
|
await pageManager.logsPage.clickMonacoEditor();
|
|
|
|
// Run query to populate results first
|
|
await pageManager.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Verify common fields group header is present.
|
|
// When applyFieldGrouping is active (semantic grouping), the header renders as
|
|
// "Common" or "Common (N)"; legacy path renders "Common Group Fields".
|
|
const cell = await pageManager.logsPage.getCellByName(/^Common/);
|
|
const cellText = await cell.textContent();
|
|
expect(cellText).toMatch(/Common/);
|
|
|
|
// Verify both streams are selected in the index list
|
|
await pageManager.logsPage.expectLogsSearchIndexListContainsText('e2e_automate, e2e_stream1');
|
|
|
|
// Execute query and navigate time picker using POM
|
|
await pageManager.logsPage.selectRunQuery();
|
|
await pageManager.logsPage.clickDateTimeButton();
|
|
await pageManager.logsPage.selectRelative6Hours();
|
|
await pageManager.logsPage.clickTimestampColumnMenu();
|
|
}
|
|
|
|
|
|
test("should add a function and display it in streams", {
|
|
tag: ['@vrl', '@function', '@multistream', '@all']
|
|
}, async ({ page }) => {
|
|
const pageManager = new PageManager(page);
|
|
|
|
await multistreamselect(page);
|
|
await pageManager.logsPage.fillMonacoEditor('.a=2');
|
|
await page.waitForTimeout(1000);
|
|
await applyQueryButton(page);
|
|
await pageManager.logsPage.clickTableExpandMenuFirst();
|
|
await pageManager.logsPage.expectVrlFunctionVisible(".a=2");
|
|
await pageManager.logsPage.expectLogsTableVisible();
|
|
await pageManager.logsPage.selectRunQuery();
|
|
});
|
|
|
|
test("should redirect to logs after clicking on stream explorer via stream page", {
|
|
tag: ['@navigation', '@streamExplorer', '@multistream', '@all']
|
|
}, async ({ page }) => {
|
|
const pageManager = new PageManager(page);
|
|
|
|
await multistreamselect(page);
|
|
await pageManager.logsPage.clickDateTimeButton();
|
|
await pageManager.logsPage.navigateToStreams();
|
|
await page.waitForTimeout(1000);
|
|
await pageManager.logsPage.navigateToStreamsAlternate();
|
|
await pageManager.logsPage.searchStreamByPlaceholder("e2e");
|
|
await page.waitForTimeout(1000);
|
|
await pageManager.logsPage.clickFirstExploreButton();
|
|
await page.waitForTimeout(1000);
|
|
await expect(page.url()).toContain("logs");
|
|
});
|
|
|
|
test("should click on interesting fields icon and display query in editor", {
|
|
tag: ['@interestingFields', '@multistream']
|
|
}, async ({ page }) => {
|
|
const pageManager = new PageManager(page);
|
|
testLogger.info('Testing interesting fields with multistream selection');
|
|
|
|
// Deliberately NOT using multistreamselect() (e2e_automate + e2e_stream1) here:
|
|
// e2e_automate is a shared fixture ingested into by unrelated tests across the whole
|
|
// suite, so its schema only grows over time. The app auto-marks fields from
|
|
// zoConfig.default_quick_mode_fields (service_name, span_id, trace_id, etc.) as
|
|
// "interesting" the moment they exist in a selected stream's schema — if e2e_automate
|
|
// has picked one up but e2e_stream1 hasn't, the resulting multi-stream UNION ALL BY
|
|
// NAME query selects that field from both streams and the one missing it 500s with a
|
|
// schema error, with zero relation to what this test actually exercises. Two freshly
|
|
// ingested, identically-shaped streams keep this test deterministic regardless of what
|
|
// any other test does to the shared fixtures.
|
|
const testRunId = Date.now().toString(36);
|
|
const { streamA, streamB } = await pageManager.ingestionPage.ingestionJoinUnion(testRunId);
|
|
|
|
await page.goto(`${logData.logsUrl}?org_identifier=${process.env["ORGNAME"]}`);
|
|
await pageManager.logsPage.selectStream(streamA);
|
|
await pageManager.logsPage.applyQueryButton(logData.logsUrl);
|
|
await pageManager.logsPage.fillStreamFilter(streamB);
|
|
await page.waitForTimeout(2000);
|
|
await pageManager.logsPage.toggleStreamSelection(streamB);
|
|
await page.waitForTimeout(4000);
|
|
await pageManager.logsPage.expectLogsSearchIndexListContainsText(`${streamA}, ${streamB}`);
|
|
|
|
// The interesting-field (ⓘ) button only renders when quick mode is on
|
|
// (FieldExpansion.vue v-if="showQuickMode"), and surfaces on row hover.
|
|
await pageManager.logsPage.enableQuickModeIfDisabled();
|
|
await pageManager.logsPage.clickAllFieldsButton();
|
|
await pageManager.logsPage.ensureQuickModeState(true);
|
|
|
|
// Search for job field using POM
|
|
await pageManager.logsPage.searchFieldByName('job');
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Hover the field row so its action buttons render, then click ⓘ
|
|
await pageManager.logsPage.hoverAndClickInterestingFieldLast('job');
|
|
|
|
// Enable SQL mode using POM
|
|
await pageManager.logsPage.enableSQLMode();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Run query using POM
|
|
await pageManager.logsPage.clickSearchBarRefreshButton();
|
|
|
|
// Click on first result
|
|
await pageManager.logsPage.clickLogTableColumnSource();
|
|
|
|
// Verify table is visible
|
|
await pageManager.logsPage.expectLogsTableVisible();
|
|
testLogger.info('Interesting fields test completed');
|
|
});
|
|
|
|
test("should display results in selected time when multiple stream selected", {
|
|
tag: ['@timePicker', '@dateTime', '@multistream', '@all']
|
|
}, async ({ page }) => {
|
|
const pageManager = new PageManager(page);
|
|
await multistreamselect(page);
|
|
await pageManager.logsPage.setDateTimeToToday();
|
|
await pageManager.logsPage.expectLogsSearchIndexListContainsText('e2e_automate, e2e_stream1');
|
|
|
|
});
|
|
|
|
test("should not show error when histogram toggle is on with multiple streams @multi-histogram @multistream @regression", async ({
|
|
page,
|
|
}) => {
|
|
const pageManager = new PageManager(page);
|
|
|
|
// Navigate to logs page using POM
|
|
await pageManager.logsPage.navigateToHome();
|
|
await pageManager.logsPage.navigateToLogs();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Select first stream (e2e_automate) using POM
|
|
await pageManager.logsPage.selectStream("e2e_automate");
|
|
await pageManager.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Select additional stream using POM
|
|
await pageManager.logsPage.fillStreamFilter("e2e_stream1");
|
|
await page.waitForTimeout(2000);
|
|
await pageManager.logsPage.toggleStreamSelection("e2e_stream1");
|
|
await page.waitForTimeout(4000);
|
|
|
|
// Ensure quick mode is ON so the All Fields toggle is visible
|
|
// (quickMode default depends on whether localStorage has saved streams from prior navigation)
|
|
await pageManager.logsPage.enableQuickModeIfDisabled();
|
|
await pageManager.logsPage.clickAllFieldsButton();
|
|
await pageManager.logsPage.toggleQueryModeEditor();
|
|
await pageManager.logsPage.clickMonacoEditor();
|
|
|
|
// Enable histogram using POM - ensure it's actually enabled
|
|
await pageManager.logsPage.toggleHistogram();
|
|
await pageManager.logsPage.toggleHistogram(); // Double click to ensure ON state
|
|
|
|
await pageManager.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Verify no histogram error is displayed
|
|
await pageManager.logsPage.expectErrorWhileFetchingNotVisible();
|
|
});
|
|
|
|
// Enhanced multistream tests
|
|
test("should verify search filter functionality with multiple streams selected", {
|
|
tag: ['@filter', '@invalidfield', '@multistream']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing search filter functionality with multiple streams');
|
|
|
|
const pageManager = new PageManager(page);
|
|
|
|
// Setup multistream selection using helper
|
|
await setupBasicMultistream(page);
|
|
|
|
// Add query in query editor and run
|
|
await pageManager.logsPage.fillQueryEditorWithRole(MULTISTREAM_CONFIG.TEST_DATA.QUERIES.INVALID_FIELD);
|
|
await pageManager.logsPage.clickSearchBarRefreshButton();
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.QUERY_EXECUTION);
|
|
|
|
testLogger.info('Search filter steps executed successfully with multiple streams');
|
|
});
|
|
|
|
test("should add a function and display it in streams with multiple streams", {
|
|
tag: ['@vrl', '@function', '@transformation']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing Monaco editor function with multiple streams');
|
|
|
|
const pageManager = new PageManager(page);
|
|
|
|
// Setup multiple streams using existing multistreamselect function
|
|
await multistreamselect(page);
|
|
|
|
// Fill Monaco editor with VRL function using constant
|
|
await pageManager.logsPage.fillMonacoEditor(MULTISTREAM_CONFIG.TEST_DATA.QUERIES.VRL_FUNCTION);
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.UI_INTERACTION);
|
|
await applyQueryButton(page);
|
|
await pageManager.logsPage.clickTableExpandMenuFirst();
|
|
|
|
// Verify VRL function is visible and table is displayed
|
|
await pageManager.logsPage.expectVrlFunctionVisible(MULTISTREAM_CONFIG.TEST_DATA.QUERIES.VRL_FUNCTION);
|
|
await pageManager.logsPage.expectLogsTableVisible();
|
|
await pageManager.logsPage.selectRunQuery();
|
|
|
|
testLogger.info('Monaco editor function test with multiple streams completed successfully');
|
|
});
|
|
|
|
test("should display combined results when both streams have data", {
|
|
tag: ['@multistreamdata', '@datavalidation', '@multistreams']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing combined results display when both streams have data');
|
|
|
|
const pageManager = new PageManager(page);
|
|
|
|
// Ensure both streams have data by ingesting test data
|
|
const orgId = process.env["ORGNAME"];
|
|
const headers = getHeaders();
|
|
const streams = MULTISTREAM_CONFIG.TEST_DATA.STREAMS;
|
|
const payloads = MULTISTREAM_CONFIG.TEST_DATA.PAYLOADS;
|
|
|
|
await sendRequest(page, getIngestionUrl(orgId, streams[0]), payloads.STREAM1, headers);
|
|
await sendRequest(page, getIngestionUrl(orgId, streams[1]), payloads.STREAM2, headers);
|
|
|
|
// Wait for data to be indexed
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.DATA_INDEXING);
|
|
|
|
// Setup multiple streams
|
|
await multistreamselect(page);
|
|
|
|
// Verify both streams are selected
|
|
await verifyMultistreamSelection(page);
|
|
|
|
// Run query to get combined results
|
|
await pageManager.logsPage.selectRunQuery();
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.QUERY_EXECUTION);
|
|
|
|
// Verify results table is visible and contains data
|
|
await pageManager.logsPage.expectLogsTableVisible();
|
|
|
|
// Verify we have results from both streams
|
|
await validateCombinedStreamResults(page, payloads);
|
|
|
|
// Verify no error messages are present
|
|
await pageManager.logsPage.expectNotificationErrorNotVisible();
|
|
|
|
testLogger.info('Combined results from both streams test completed successfully');
|
|
});
|
|
|
|
test("should create and delete saved view with multiple streams selected", {
|
|
tag: ['@savedview', '@viewcrud', '@multiconfig']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing saved view creation and deletion with multiple streams');
|
|
|
|
const pageManager = new PageManager(page);
|
|
const streams = MULTISTREAM_CONFIG.TEST_DATA.STREAMS;
|
|
|
|
// Setup multiple streams selection using existing function
|
|
await multistreamselect(page);
|
|
|
|
// Verify both streams are selected using helper
|
|
await verifyMultistreamSelection(page);
|
|
|
|
// Generate a random saved view name with multistream prefix
|
|
const randomSavedViewName = `multistream_view_${Math.random().toString(36).substring(2, 10)}`;
|
|
testLogger.info(`Creating saved view with name: ${randomSavedViewName}`);
|
|
|
|
// Create saved view with multistream configuration
|
|
await pageManager.logsPage.clickSavedViewsExpand();
|
|
await pageManager.logsPage.clickSaveViewButton();
|
|
await pageManager.logsPage.fillSavedViewName(randomSavedViewName);
|
|
await pageManager.logsPage.clickSavedViewDialogSave();
|
|
|
|
// Wait for success message with timeout constant
|
|
try {
|
|
await pageManager.logsPage.getSuccessToastLocator().waitFor({
|
|
state: 'visible',
|
|
timeout: MULTISTREAM_CONFIG.TIMEOUTS.DATA_INDEXING
|
|
});
|
|
testLogger.info('Success toast validated: Multistream view created successfully');
|
|
} catch (error) {
|
|
testLogger.info('View creation toast may have appeared and disappeared quickly - continuing with test');
|
|
}
|
|
|
|
// Wait for saved view creation to complete using constant
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.NAVIGATION);
|
|
|
|
// Navigate away and back to verify saved view persistence
|
|
await pageManager.logsPage.clickStreamsMenuItem();
|
|
await pageManager.logsPage.clickSearchStreamInput();
|
|
await pageManager.logsPage.fillSearchStreamInput('e2e');
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.TOAST_WAIT);
|
|
await pageManager.logsPage.clickExploreButton();
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.NAVIGATION);
|
|
|
|
// Verify saved view exists and can be loaded
|
|
await pageManager.logsPage.waitForSavedViewsButton();
|
|
await pageManager.logsPage.clickSavedViewsExpand();
|
|
await pageManager.logsPage.clickSavedViewSearchInput();
|
|
await pageManager.logsPage.fillSavedViewSearchInput(randomSavedViewName);
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.UI_INTERACTION);
|
|
|
|
// Apply the saved view
|
|
await pageManager.logsPage.waitForSavedViewText(randomSavedViewName);
|
|
await pageManager.logsPage.clickSavedViewByText(randomSavedViewName);
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.NAVIGATION);
|
|
|
|
// Verify that multistream configuration was saved and restored using helper
|
|
await verifyMultistreamSelection(page);
|
|
testLogger.info('Verified: Saved view correctly restored multistream selection');
|
|
|
|
// Verify query results are displayed
|
|
await pageManager.logsPage.expectLogsTableVisible();
|
|
testLogger.info('Verified: Query results are displayed after loading saved view');
|
|
|
|
// Clean up: Try to delete the saved view (with error handling)
|
|
try {
|
|
await pageManager.logsPage.clickSavedViewsExpand();
|
|
await pageManager.logsPage.clickSavedViewSearchInput();
|
|
await pageManager.logsPage.fillSavedViewSearchInput(randomSavedViewName);
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.UI_INTERACTION);
|
|
|
|
// Delete the saved view using POM
|
|
await pageManager.logsPage.clickDeleteSavedViewButton(randomSavedViewName);
|
|
await page.waitForTimeout(MULTISTREAM_CONFIG.TIMEOUTS.TOAST_WAIT);
|
|
await pageManager.logsPage.clickConfirmButton();
|
|
|
|
testLogger.info(`Successfully deleted multistream saved view: ${randomSavedViewName}`);
|
|
} catch (cleanupError) {
|
|
testLogger.warn(`Cleanup failed - saved view may still exist: ${randomSavedViewName}`, { error: cleanupError.message });
|
|
// Test is still successful even if cleanup fails
|
|
}
|
|
|
|
testLogger.info('Multistream saved view creation and deletion test completed successfully');
|
|
});
|
|
})
|