Files
openobserve/tests/ui-testing/playwright-tests/Logs/indexquery.spec.js
T

377 lines
15 KiB
JavaScript

const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
const logData = require("../../fixtures/log.json");
const logsdata = require("../../../test-data/logs_data.json");
const PageManager = require('../../pages/page-manager.js');
const testLogger = require('../utils/test-logger.js');
const { ingestTestData } = require('../utils/data-ingestion.js');
const { getAuthHeaders, getOrgIdentifier } = require('../utils/cloud-auth.js');
const { ingestBase } = require('../utils/ingest-url.js');
// Utility Functions
// Legacy login function replaced by global authentication via navigateToBase
const selectStream = async (pm, stream) => {
// Stream selection UI stabilization - deterministic wait keyed on the index-dropdown wrapper
await pm.logsPage.waitForStreamSelectReady();
await pm.logsPage.selectStream(stream);
};
async function applyQueryButton(pm) {
// Query preparation - deterministic wait keyed on refresh button readiness
await pm.logsPage.waitForRefreshButtonReady();
const search = pm.page.waitForResponse(
(response) => /\/api\/.+\/_search/.test(response.url()),
{ timeout: 30000 }
);
await pm.logsPage.clickRefreshButton();
const response = await search;
expect(response.status()).toBe(200);
}
async function runQuery(page, query) {
const startTime = Date.now();
const response = await page.evaluate(async ({ query, url, streamName, orgId }) => {
try {
const fetchUrl = `${url}/api/${orgId}/${streamName}/_search?type=logs`;
const response = await fetch(fetchUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(query)
});
if (!response.ok) {
const errorText = await response.text();
console.error('Query failed', response.status, errorText); // browser-context (page.evaluate) — testLogger unavailable
return { error: errorText, status: response.status };
}
const result = await response.json();
return result;
} catch (err) {
console.error('Query execution error', err.message); // browser-context (page.evaluate) — testLogger unavailable
return { error: err.message };
}
}, {
query,
url: process.env.ZO_BASE_URL,
streamName: "e2e_automate",
orgId: getOrgIdentifier()
});
const endTime = Date.now();
const duration = endTime - startTime;
return {
response,
duration
};
}
test.describe("Compare SQL query execution times", () => {
test.describe.configure({ mode: 'parallel' });
let pm; // Page Manager instance
test.beforeEach(async ({ page }, testInfo) => {
// Initialize test setup
testLogger.testStart(testInfo.title, testInfo.file);
// Navigate to base URL with authentication
await navigateToBase(page);
pm = new PageManager(page);
// Post-authentication stabilization - deterministic wait keyed on DOM-ready state
await page.waitForLoadState('domcontentloaded');
// Data ingestion for performance testing
await ingestTestData(page);
// Navigate to logs page and setup for performance testing
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier()}`);
await selectStream(pm, logData.Stream);
await applyQueryButton(pm);
testLogger.info('Performance test setup completed');
});
test("should compare match_all_raw and match_all query times", async ({ page }) => {
testLogger.info('Testing match_all_raw vs match_all query performance comparison');
const oneMinuteAgo = Date.now() - 60 * 1000;
const query1 = {
query: {
sql: "select * from 'e2e_automate' WHERE match_all_raw('provide_credentials')",
start_time: oneMinuteAgo,
end_time: Date.now(),
from: 0,
size: 250,
quick_mode: false
},
regions: []
};
const query2 = {
query: {
sql: "select * from 'e2e_automate' WHERE match_all('provide_credentials')",
start_time: oneMinuteAgo,
end_time: Date.now(),
from: 0,
size: 250,
quick_mode: false
},
regions: []
};
const result1 = await runQuery(page, query1);
const result2 = await runQuery(page, query2);
if (!result1.response || result1.response.error) {
testLogger.error('Query 1 failed to execute', { query: 'match_all_raw', error: result1.response?.error });
return;
}
if (!result2.response || result2.response.error) {
testLogger.error('Query 2 failed to execute', { query: 'match_all', error: result2.response?.error });
return;
}
testLogger.info('Query 1 performance result', { query: 'match_all_raw', duration: result1.duration, records: result1.response.total || 0 });
testLogger.info('Query 2 performance result', { query: 'match_all', duration: result2.duration, records: result2.response.total || 0 });
try {
expect(result2.duration).toBeLessThan(result1.duration);
testLogger.info('Performance assertion passed', { test: 'match_all vs match_all_raw', result: 'match_all faster' });
testLogger.info('Performance comparison completed successfully', {
query1_duration: result1.duration,
query2_duration: result2.duration
});
} catch (error) {
testLogger.warn('Performance assertion failed', { test: 'match_all vs match_all_raw', result: 'match_all slower than expected' });
testLogger.warn('Performance assertion failed', { error: error.message });
}
});
test("should compare match_all and match_all_raw_ignore_case query times", async ({ page }) => {
testLogger.info('Testing match_all vs match_all_raw_ignorecase query performance comparison');
const oneMinuteAgo = Date.now() - 60 * 1000;
const query1 = {
query: {
sql: "select * from 'e2e_automate' WHERE match_all('provide_credentials')",
start_time: oneMinuteAgo,
end_time: Date.now(),
from: 0,
size: 250,
quick_mode: false
},
regions: []
};
const query2 = {
query: {
sql: "select * from 'e2e_automate' WHERE match_all_raw_ignorecase('provide_credentials')",
start_time: oneMinuteAgo,
end_time: Date.now(),
from: 0,
size: 250,
quick_mode: false
},
regions: []
};
const result1 = await runQuery(page, query1);
const result2 = await runQuery(page, query2);
if (!result1.response || result1.response.error) {
testLogger.error('Query 1 failed to execute', { query: 'match_all', error: result1.response?.error });
return;
}
if (!result2.response || result2.response.error) {
testLogger.error('Query 2 failed to execute', { query: 'match_all_raw_ignorecase', error: result2.response?.error });
return;
}
testLogger.info('Query 1 performance result', { query: 'match_all', duration: result1.duration, records: result1.response.total || 0 });
testLogger.info('Query 2 performance result', { query: 'match_all_raw_ignorecase', duration: result2.duration, records: result2.response.total || 0 });
try {
expect(result1.duration).toBeLessThan(result2.duration);
testLogger.info('Performance assertion passed', { test: 'match_all vs match_all_raw_ignorecase', result: 'match_all faster' });
testLogger.info('Performance comparison completed successfully', {
query1_duration: result1.duration,
query2_duration: result2.duration
});
} catch (error) {
testLogger.warn('Performance assertion failed', { test: 'match_all vs match_all_raw_ignorecase', result: 'match_all slower than expected' });
testLogger.warn('Performance assertion failed', { error: error.message });
}
});
test("should validate time range filtering with custom timestamped data", {
tag: ['@logs', '@timeFilter', '@dataIngestion', '@critical', '@all']
}, async ({ page }) => {
testLogger.info('Testing time range filtering with custom timestamped data ingestion');
// This test validates that OpenObserve correctly filters logs based on their actual timestamp
// (not ingestion time) when backdated data is ingested. Real-world scenarios include:
// - Log ingestion delays: Logs arrive late with past timestamps
// - Batch processing: Historical data being processed and ingested
// - Multi-source logs: Different systems sending logs with their local timestamps
// - Time zone handling: Logs from different time zones with past timestamps
const orgId = getOrgIdentifier();
const streamName = `e2e_time_test_${Date.now()}`; // Use timestamp for uniqueness
const headers = getAuthHeaders();
// Create test data with different timestamps (within 5-hour ingestion limit)
const now = Date.now();
const oneHourAgo = now - (1 * 60 * 60 * 1000);
const threeHoursAgo = now - (3 * 60 * 60 * 1000);
const fourHoursAgo = now - (4 * 60 * 60 * 1000);
const testLogs = [
{
_timestamp: new Date(oneHourAgo).toISOString(),
message: "Recent log entry - 1 hour ago",
level: "info",
test_id: "time_test_1h",
data_age: "1h_old",
hours_old: 1,
expected_in_30m_range: false,
expected_in_2h_range: true,
expected_in_6h_range: true
},
{
_timestamp: new Date(threeHoursAgo).toISOString(),
message: "Three hour old log entry - 3 hours ago",
level: "info",
test_id: "time_test_3h",
data_age: "3h_old",
hours_old: 3,
expected_in_30m_range: false,
expected_in_2h_range: false,
expected_in_6h_range: true
},
{
_timestamp: new Date(fourHoursAgo).toISOString(),
message: "Four hour old log entry - 4 hours ago",
level: "info",
test_id: "time_test_4h",
data_age: "4h_old",
hours_old: 4,
expected_in_30m_range: false,
expected_in_2h_range: false,
expected_in_6h_range: true
}
];
// Ingest test data with specific timestamps
const ingestionResponse = await page.evaluate(async ({ url, headers, orgId, streamName, testLogs }) => {
const fetchResponse = await fetch(`${url}/api/${orgId}/${streamName}/_json`, {
method: 'POST',
headers: headers,
body: JSON.stringify(testLogs)
});
return await fetchResponse.json();
}, {
url: ingestBase(process.env.INGESTION_URL),
headers: headers,
orgId: orgId,
streamName: streamName,
testLogs: testLogs
});
testLogger.debug('Test data ingested', { response: ingestionResponse });
// Verify ingestion was successful
expect(ingestionResponse.code).toBe(200);
expect(ingestionResponse.status[0].successful).toBe(3);
expect(ingestionResponse.status[0].failed).toBe(0);
testLogger.info('Verified all 3 test logs were successfully ingested');
// Wait for data to be indexed and available - poll the streams API until the new stream is discoverable
await expect.poll(async () => {
return await page.evaluate(async ({ url, headers, orgId, streamName }) => {
try {
const res = await fetch(`${url}/api/${orgId}/streams?type=logs`, { headers });
if (!res.ok) return false;
const data = await res.json();
const list = Array.isArray(data) ? data : (data?.list || []);
return list.some((s) => s.name === streamName);
} catch {
return false;
}
}, { url: process.env.INGESTION_URL, headers, orgId, streamName });
}, { intervals: [1000, 1000, 1500, 2000], timeout: 30000 }).toBe(true);
// Navigate to logs page and select test stream
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier()}`);
await pm.logsPage.selectStream(streamName);
// STEP 0: First verify ALL data is available in wide range (baseline check to eliminate false positives)
await pm.logsPage.clickDateTimeButton();
await pm.logsPage.clickPast6DaysButton();
await applyQueryButton(pm);
// Verify all 3 logs are available (proves ingestion and indexing worked)
await pm.logsPage.expectLogsTableVisible();
await pm.logsPage.expectLogsTableRowCount(3);
await pm.logsPage.waitForSearchResultAndCheckText("Recent log entry - 1 hour ago");
await pm.logsPage.waitForSearchResultAndCheckText("Three hour old log entry - 3 hours ago");
await pm.logsPage.waitForSearchResultAndCheckText("Four hour old log entry - 4 hours ago");
testLogger.info('Baseline check passed: All 3 logs are available in 6-day range');
// Test 1: Set time range to last 30 minutes - should show NO logs (all are older than 30 min)
await pm.logsPage.clickDateTimeButton();
await pm.logsPage.clickRelative15MinButton(); // Using 15min as closest to 30min
await applyQueryButton(pm);
// Verify exactly 0 rows are visible (all data is older than 15 minutes)
await pm.logsPage.expectLogsTableRowCount(0);
testLogger.info('15-minute range test passed: No data visible (all logs older than 15 minutes)');
// Test 2: Set time range to last 2 hours - should show exactly 1 log (1-hour old log only)
await pm.logsPage.clickDateTimeButton();
await pm.logsPage.selectRelative6Hours(); // Using 6h since 2h might not exist
await applyQueryButton(pm);
// Verify all 3 logs are visible (all within 6 hours). Assert on the log
// message (the FTS "body" field shown in the default view) which uniquely
// identifies the row; the non-body "data_age" field is not in the default
// columns and is covered by the message text already.
await pm.logsPage.expectLogsTableRowCount(3);
await pm.logsPage.waitForSearchResultAndCheckText("Recent log entry - 1 hour ago");
testLogger.info('6-hour range test passed: All 3 logs visible (all within 6 hours)');
// Test 3: Return to 6 days - should show all 3 logs again (final verification)
await pm.logsPage.clickDateTimeButton();
await pm.logsPage.clickPast6DaysButton();
await applyQueryButton(pm);
// Verify all 3 logs are visible again (confirms filtering is reversible).
// Assert on the log messages (the FTS "body" field shown by default), which
// uniquely identify each row; the non-body "data_age" values are redundant
// with these and are not in the default columns.
await pm.logsPage.expectLogsTableRowCount(3);
await pm.logsPage.waitForSearchResultAndCheckText("Recent log entry - 1 hour ago");
await pm.logsPage.waitForSearchResultAndCheckText("Three hour old log entry - 3 hours ago");
await pm.logsPage.waitForSearchResultAndCheckText("Four hour old log entry - 4 hours ago");
testLogger.info('Final verification passed: All 3 logs visible in 6-day range');
testLogger.info('Time range filtering validation completed successfully - Progressive filtering works: 0 → 3 → 3 logs as range expands');
testLogger.info('Time range filtering validation test completed successfully');
});
test.afterEach(async () => {
try {
// await pm.commonActions.flipStreaming();
testLogger.info('Streaming flipped after test');
} catch (error) {
testLogger.warn('Streaming flip failed', { error: error.message });
}
});
});