mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 00:08:55 +00:00
test: fix failing alpha1 e2e tests (#15494)
This commit is contained in:
@@ -169,6 +169,14 @@ export default class ChartTypeSelector {
|
||||
'[data-test="index-dropdown-stream"]'
|
||||
);
|
||||
|
||||
// An already-selected stream can be missing from the open option list (seen on alpha1), so there is nothing to click.
|
||||
const trigger = this.page.locator('[data-test="index-dropdown-stream-trigger"]').first();
|
||||
await streamInput.waitFor({ state: "visible", timeout: 10000 });
|
||||
if ((await trigger.getAttribute("data-test-selected-value", { timeout: 5000 })) === streamName) {
|
||||
testLogger.info(`selectStream: "${streamName}" is already the selected stream`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
// Close any open dropdown first — click outside (ODropdown closes on outside click)
|
||||
@@ -189,8 +197,9 @@ export default class ChartTypeSelector {
|
||||
.allTextContents();
|
||||
testLogger.debug(`Attempt ${attempt}: Looking for "${streamName}". Available options (${allOptions.length}): ${allOptions.slice(0, 10).join(', ')}`);
|
||||
|
||||
// Exact value match: hasText is a substring match, so "e2e_automate" also hit "e2e_automate1_w0".
|
||||
const streamOption = this.page
|
||||
.locator('[data-test="index-dropdown-stream-option"]', { hasText: streamName })
|
||||
.locator(`[data-test="index-dropdown-stream-option"][data-test-value="${streamName}"]`)
|
||||
.first();
|
||||
|
||||
await streamOption.waitFor({ state: "visible", timeout: 15000 });
|
||||
@@ -708,6 +717,16 @@ export default class ChartTypeSelector {
|
||||
testLogger.debug('Entered custom SQL query', { query });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the custom SQL has been parsed into these axis fields; Apply validates the panel state, which lags the editor.
|
||||
* @param {string} xAlias
|
||||
* @param {string} yAlias
|
||||
*/
|
||||
async waitForCustomQueryAxes(xAlias, yAlias) {
|
||||
await this.page.locator(`[data-test="dashboard-x-item-${xAlias}"]`).first().waitFor({ state: "attached", timeout: 15000 });
|
||||
await this.page.locator(`[data-test="dashboard-y-item-${yAlias}"]`).first().waitFor({ state: "attached", timeout: 15000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* End-to-end helper: switch to SQL → Custom query mode and enter the query.
|
||||
* @param {string} query - The SQL query to enter
|
||||
|
||||
@@ -962,26 +962,10 @@ export class LogsPage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the current page is this org's logs page and was loaded moments ago, so it has no state to reset.
|
||||
* @param {string} orgId
|
||||
* @param {number} maxAgeMs
|
||||
*/
|
||||
async isFreshLogsPage(orgId, maxAgeMs = 15000) {
|
||||
let url;
|
||||
try { url = new URL(this.page.url()); } catch { return false; }
|
||||
if (!url.pathname.endsWith('/web/logs') || url.searchParams.get('org_identifier') !== orgId) return false;
|
||||
const ageMs = await this.page.evaluate(() => performance.now()).catch(() => Infinity);
|
||||
return ageMs < maxAgeMs;
|
||||
}
|
||||
|
||||
async selectStream(stream, maxRetries = 5, apiWaitMs = null, skipNavigation = false) {
|
||||
// Cloud environments need longer for streams to be indexed after ingestion
|
||||
const effectiveApiWaitMs = apiWaitMs ?? (isCloudEnvironment() ? 120000 : 30000);
|
||||
testLogger.info(`selectStream: Selecting stream: ${stream} (apiWait: ${effectiveApiWaitMs}ms, skipNavigation: ${skipNavigation})`);
|
||||
const orgId = getOrgIdentifier();
|
||||
// Measured before the API wait, which takes time but leaves the UI untouched.
|
||||
const freshLogsPage = !skipNavigation && await this.isFreshLogsPage(orgId);
|
||||
|
||||
// First, wait for the stream to be available via API (skip if apiWaitMs is 0)
|
||||
if (effectiveApiWaitMs > 0) {
|
||||
@@ -998,11 +982,10 @@ export class LogsPage {
|
||||
// Navigate to logs page via URL to ensure fresh stream list.
|
||||
// skipNavigation=true bypasses page.goto when the caller is already
|
||||
// on the logs page and wants to avoid auto-triggering a query.
|
||||
const orgId = getOrgIdentifier();
|
||||
const logsUrl = `${process.env.ZO_BASE_URL}/web/logs?org_identifier=${orgId}`;
|
||||
|
||||
if (freshLogsPage) {
|
||||
testLogger.info('selectStream: Logs page just loaded for this org — skipping reload (retries still reload if the stream is missing)');
|
||||
} else if (!skipNavigation) {
|
||||
if (!skipNavigation) {
|
||||
testLogger.info(`selectStream: Navigating to logs page: ${logsUrl}`);
|
||||
await this.page.goto(logsUrl, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {});
|
||||
// Wait for the index-dropdown wrapper to mount instead of a hard timeout.
|
||||
@@ -1706,6 +1689,11 @@ export class LogsPage {
|
||||
await this.enableSqlModeIfNeeded();
|
||||
}
|
||||
|
||||
async expectSqlModeActive() {
|
||||
// Filter mode rejects LIMIT client-side, so a run before this flips sends no request at all.
|
||||
await expect.poll(() => this._isSqlModeEnabledViaVue(), { timeout: 10000 }).toBe(true);
|
||||
}
|
||||
|
||||
// Quick Mode methods (now inside the utilities hamburger menu)
|
||||
async verifyQuickModeToggle() {
|
||||
const quickModeToggle = this.page.locator(this.quickModeToggle);
|
||||
|
||||
@@ -75,6 +75,7 @@ export class PipelinesFormValidationPage {
|
||||
this.associateFunctionDrawer = '[data-test="associate-function-drawer"]';
|
||||
this.associateFunctionSelectPopover = '[data-test="associate-function-select-function-input-popover"]';
|
||||
this.associateFunctionSelectError = '[data-test="associate-function-select-function-input-error"]';
|
||||
this.associateFunctionSelectTrigger = '[data-test="associate-function-select-function-input-trigger"]';
|
||||
this.createFunctionToggle = '[data-test="create-function-toggle"]';
|
||||
this.associateFunctionAfterFlattening = '[data-test="associate-function-after-flattening-toggle"]';
|
||||
|
||||
@@ -303,6 +304,17 @@ export class PipelinesFormValidationPage {
|
||||
return this.page.locator(this.associateFunctionSelectError);
|
||||
}
|
||||
|
||||
// FunctionPicker shows a spinner instead of the form until the functions list loads.
|
||||
getAssociateFunctionSelectTriggerLocator() {
|
||||
return this.page.locator(this.associateFunctionSelectTrigger);
|
||||
}
|
||||
|
||||
getAssociateFunctionSaveBlockedLocator() {
|
||||
return this.page
|
||||
.locator(this.associateFunctionSelectError)
|
||||
.or(this.page.locator(`${this.associateFunctionSaveBtn}:disabled`));
|
||||
}
|
||||
|
||||
getCreateFunctionToggleLocator() {
|
||||
return this.page.locator(this.createFunctionToggle);
|
||||
}
|
||||
|
||||
@@ -861,10 +861,13 @@ test.describe("ConfigPanel — PromQL Settings", () => {
|
||||
if (url.includes("/api/")) allCalls.push({ url, status: res.status() });
|
||||
};
|
||||
page.on("response", onResponse);
|
||||
// The settled signal can pass before the query is even sent on a slow env, so wait for the query's response headers first.
|
||||
const promqlResponse = page.waitForResponse((res) => isPromQLQuery(res.url()), { timeout: 30000 });
|
||||
|
||||
// One Apply, then wait on the panel's own completion signal rather than a
|
||||
// network event — the Apply button stays disabled until every chunk lands.
|
||||
await pm.dashboardPanelActions.applyDashboardBtn();
|
||||
await promqlResponse;
|
||||
const settled = await waitForPanelRenderSettled(page, pm);
|
||||
page.off("response", onResponse);
|
||||
|
||||
|
||||
@@ -423,6 +423,7 @@ test.describe(
|
||||
const sqlQuery = `SELECT histogram(_timestamp) as "x_axis_1", count(*) as "y_axis_1" FROM "e2e_automate" WHERE kubernetes_container_name = '{{variablename}}' GROUP BY x_axis_1 ORDER BY x_axis_1`;
|
||||
|
||||
await pm.chartTypeSelector.enterCustomSQL(sqlQuery);
|
||||
await pm.chartTypeSelector.waitForCustomQueryAxes("x_axis_1", "y_axis_1");
|
||||
|
||||
// Apply the query
|
||||
await pm.dashboardPanelActions.applyDashboardBtn();
|
||||
@@ -844,6 +845,7 @@ test.describe(
|
||||
const sqlQuery = `SELECT histogram(_timestamp) as "x_axis_1", count(*) as "y_axis_1" FROM "e2e_automate" WHERE kubernetes_container_name = '{{ variablename }}' GROUP BY x_axis_1 ORDER BY x_axis_1`;
|
||||
|
||||
await pm.chartTypeSelector.enterCustomSQL(sqlQuery);
|
||||
await pm.chartTypeSelector.waitForCustomQueryAxes("x_axis_1", "y_axis_1");
|
||||
|
||||
// Apply the query
|
||||
await pm.dashboardPanelActions.applyDashboardBtn();
|
||||
|
||||
+2
@@ -299,6 +299,8 @@ test.describe("Dashboard Panel Time - Apply Button Behavior", () => {
|
||||
|
||||
test("24-should not apply changes when Apply is not clicked in Edit Panel mode", async ({ page }) => {
|
||||
const pm = new PageManager(page);
|
||||
// This test covers the Apply gate; Auto Run (on where auto_query_enabled) applies picks without Apply.
|
||||
await pm.dashboardAutoRun.setStoredAutoRun("false");
|
||||
const timestamp = Date.now();
|
||||
const dashboardName = `Dashboard_NoApply_${timestamp}`;
|
||||
const panelName = `Panel_NoApply_${timestamp}`;
|
||||
|
||||
+2
@@ -400,6 +400,8 @@ test.describe("Dashboard Panel Time - Part 1: Configuration and Basic Behavior",
|
||||
|
||||
test("4-should change panel time with Apply button", async ({ page }) => {
|
||||
const pm = new PageManager(page);
|
||||
// This test covers the Apply gate; Auto Run (on where auto_query_enabled) applies picks without Apply.
|
||||
await pm.dashboardAutoRun.setStoredAutoRun("false");
|
||||
const timestamp = Date.now();
|
||||
const dashboardName = `Dashboard_ChangeTime_${timestamp}`;
|
||||
const panelName = `Panel_ChangeTime_${timestamp}`;
|
||||
|
||||
@@ -29,6 +29,8 @@ test.describe.configure({ mode: "parallel" });
|
||||
test.describe("Dashboard Variables - Refresh Indicators & Panel Reload", { tag: ['@dashboards', '@dashboardVariables', '@refresh', '@P1'] }, () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await navigateToBase(page);
|
||||
// These tests cover the manual-refresh indicators; Auto Run (on where auto_query_enabled) applies changes without them.
|
||||
await new PageManager(page).dashboardAutoRun.setStoredAutoRun("false");
|
||||
await ingestion(page);
|
||||
});
|
||||
|
||||
|
||||
@@ -289,19 +289,17 @@ test.describe(
|
||||
await expect(
|
||||
pm.pipelinesFormValidation.getAssociateFunctionDrawerLocator()
|
||||
).toBeVisible({ timeout: 10000 });
|
||||
// Save clicked during the functions-list spinner validates nothing, so wait for the form.
|
||||
await expect(
|
||||
pm.pipelinesFormValidation.getAssociateFunctionSelectTriggerLocator()
|
||||
).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Attempt to save without selecting a function
|
||||
const saveBtn = pm.pipelinesFormValidation.getAssociateFunctionSaveBtnLocator();
|
||||
await saveBtn.click();
|
||||
await pm.pipelinesFormValidation.getAssociateFunctionSaveBtnLocator().click();
|
||||
|
||||
const errorVisible = await pm.pipelinesFormValidation
|
||||
.getAssociateFunctionSelectErrorLocator()
|
||||
.isVisible({ timeout: 3000 })
|
||||
.catch(() => false);
|
||||
|
||||
const saveDisabled = await saveBtn.isDisabled({ timeout: 1000 }).catch(() => false);
|
||||
|
||||
expect(errorVisible || saveDisabled).toBe(true);
|
||||
await expect(
|
||||
pm.pipelinesFormValidation.getAssociateFunctionSaveBlockedLocator()
|
||||
).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -235,7 +235,10 @@ test.describe("Logs Regression Bugs", () => {
|
||||
// Enter subquery
|
||||
const subquery = 'SELECT kubernetes_pod_name FROM (SELECT * FROM "e2e_automate" WHERE kubernetes_pod_name IS NOT NULL LIMIT 10)';
|
||||
testLogger.info(`Entering subquery: ${subquery}`);
|
||||
// Bug #7751 is about SQL-mode queries; filter mode rejects the LIMIT before any request is sent.
|
||||
await pm.logsPage.enableSqlModeIfNeeded();
|
||||
await pm.logsPage.clearAndFillQueryEditor(subquery);
|
||||
await pm.logsPage.expectSqlModeActive();
|
||||
await pm.logsPage.waitForTimeout(500);
|
||||
|
||||
// Run query
|
||||
@@ -307,7 +310,10 @@ test.describe("Logs Regression Bugs", () => {
|
||||
// Enter aggregation query
|
||||
const aggQuery = 'SELECT kubernetes_pod_name, count(*) as total FROM "e2e_automate" WHERE kubernetes_pod_name IS NOT NULL GROUP BY kubernetes_pod_name LIMIT 10';
|
||||
testLogger.info(`Entering aggregation query: ${aggQuery}`);
|
||||
// Bug #7751 is about SQL-mode queries; filter mode rejects the LIMIT before any request is sent.
|
||||
await pm.logsPage.enableSqlModeIfNeeded();
|
||||
await pm.logsPage.clearAndFillQueryEditor(aggQuery);
|
||||
await pm.logsPage.expectSqlModeActive();
|
||||
await pm.logsPage.waitForTimeout(500);
|
||||
|
||||
// Run query
|
||||
|
||||
@@ -580,6 +580,10 @@ async function performGlobalIngestion(page) {
|
||||
break;
|
||||
}
|
||||
testLogger.debug(`[alpha1] e2e_automate not yet indexed, waiting...`);
|
||||
} else if (streamsResult.status === 401 || streamsResult.status === 403) {
|
||||
// The ingestion passcode is not authorized for the streams list; retrying cannot succeed.
|
||||
testLogger.warn(`[alpha1] Streams API returned ${streamsResult.status} — cannot verify e2e_automate here; tests wait for their own streams`);
|
||||
return;
|
||||
} else {
|
||||
testLogger.debug(`[alpha1] Streams API returned ${streamsResult.status}, retrying...`);
|
||||
}
|
||||
@@ -717,6 +721,11 @@ async function performGlobalIngestionWithFetch() {
|
||||
try {
|
||||
const r = await fetch(`${baseUrl}/api/${orgId}/streams?type=logs&keyword=e2e_automate`, { headers });
|
||||
lastSeen = `HTTP ${r.status}`;
|
||||
if (r.status === 401 || r.status === 403) {
|
||||
// The ingestion passcode is not authorized for the streams list; retrying cannot succeed.
|
||||
testLogger.warn(`[alpha1] Streams API returned ${r.status} — cannot verify e2e_automate here; tests wait for their own streams`);
|
||||
return;
|
||||
}
|
||||
if (r.ok) {
|
||||
const data = await r.json();
|
||||
const names = (data.list || []).map(s => s.name);
|
||||
|
||||
Reference in New Issue
Block a user