From bb0594847b04558d372c8ea5db0f43124513a87b Mon Sep 17 00:00:00 2001 From: Shrinath Rao Date: Sun, 18 Oct 2026 12:21:39 +0000 Subject: [PATCH] test: fix failing alpha1 e2e tests (#15494) --- .../pages/dashboardPages/dashboard-chart.js | 21 ++++++++++++++- tests/ui-testing/pages/logsPages/logsPage.js | 26 +++++-------------- .../pipelinesFormValidationPage.js | 12 +++++++++ .../dashboard-config-promql.spec.js | 3 +++ .../dashboard-mustache-variables.spec.js | 2 ++ ...ashboard-panel-time-apply-behavior.spec.js | 2 ++ ...shboard-panel-time-config-behavior.spec.js | 2 ++ .../dashboard-variables-refresh.spec.js | 2 ++ .../pipelines-form-validation.spec.js | 18 ++++++------- .../Logs/logs-regression.spec.js | 6 +++++ .../utils/global-setup-alpha1.js | 9 +++++++ 11 files changed, 73 insertions(+), 30 deletions(-) diff --git a/tests/ui-testing/pages/dashboardPages/dashboard-chart.js b/tests/ui-testing/pages/dashboardPages/dashboard-chart.js index 6b2bf7a12f..2d71291c27 100644 --- a/tests/ui-testing/pages/dashboardPages/dashboard-chart.js +++ b/tests/ui-testing/pages/dashboardPages/dashboard-chart.js @@ -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 diff --git a/tests/ui-testing/pages/logsPages/logsPage.js b/tests/ui-testing/pages/logsPages/logsPage.js index 35ddbfcfee..617ecad8b4 100644 --- a/tests/ui-testing/pages/logsPages/logsPage.js +++ b/tests/ui-testing/pages/logsPages/logsPage.js @@ -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); diff --git a/tests/ui-testing/pages/pipelinesPages/pipelinesFormValidationPage.js b/tests/ui-testing/pages/pipelinesPages/pipelinesFormValidationPage.js index 08d3e31cce..524f0767ba 100644 --- a/tests/ui-testing/pages/pipelinesPages/pipelinesFormValidationPage.js +++ b/tests/ui-testing/pages/pipelinesPages/pipelinesFormValidationPage.js @@ -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); } diff --git a/tests/ui-testing/playwright-tests/Dashboards/dashboard-config-promql.spec.js b/tests/ui-testing/playwright-tests/Dashboards/dashboard-config-promql.spec.js index 9eb64e3e04..dbea9eeda0 100644 --- a/tests/ui-testing/playwright-tests/Dashboards/dashboard-config-promql.spec.js +++ b/tests/ui-testing/playwright-tests/Dashboards/dashboard-config-promql.spec.js @@ -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); diff --git a/tests/ui-testing/playwright-tests/Dashboards/dashboard-mustache-variables.spec.js b/tests/ui-testing/playwright-tests/Dashboards/dashboard-mustache-variables.spec.js index a9aea91a3a..f03bc5f91a 100644 --- a/tests/ui-testing/playwright-tests/Dashboards/dashboard-mustache-variables.spec.js +++ b/tests/ui-testing/playwright-tests/Dashboards/dashboard-mustache-variables.spec.js @@ -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(); diff --git a/tests/ui-testing/playwright-tests/Dashboards/dashboard-panel-time-apply-behavior.spec.js b/tests/ui-testing/playwright-tests/Dashboards/dashboard-panel-time-apply-behavior.spec.js index 8026c03014..991f5b2874 100644 --- a/tests/ui-testing/playwright-tests/Dashboards/dashboard-panel-time-apply-behavior.spec.js +++ b/tests/ui-testing/playwright-tests/Dashboards/dashboard-panel-time-apply-behavior.spec.js @@ -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}`; diff --git a/tests/ui-testing/playwright-tests/Dashboards/dashboard-panel-time-config-behavior.spec.js b/tests/ui-testing/playwright-tests/Dashboards/dashboard-panel-time-config-behavior.spec.js index fe11ed2689..212b915999 100644 --- a/tests/ui-testing/playwright-tests/Dashboards/dashboard-panel-time-config-behavior.spec.js +++ b/tests/ui-testing/playwright-tests/Dashboards/dashboard-panel-time-config-behavior.spec.js @@ -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}`; diff --git a/tests/ui-testing/playwright-tests/Dashboards/dashboard-variables-refresh.spec.js b/tests/ui-testing/playwright-tests/Dashboards/dashboard-variables-refresh.spec.js index 06650f0dce..d7d1e507f0 100644 --- a/tests/ui-testing/playwright-tests/Dashboards/dashboard-variables-refresh.spec.js +++ b/tests/ui-testing/playwright-tests/Dashboards/dashboard-variables-refresh.spec.js @@ -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); }); diff --git a/tests/ui-testing/playwright-tests/Pipelines/pipelines-form-validation.spec.js b/tests/ui-testing/playwright-tests/Pipelines/pipelines-form-validation.spec.js index 316a1207d5..f1bdedb90d 100644 --- a/tests/ui-testing/playwright-tests/Pipelines/pipelines-form-validation.spec.js +++ b/tests/ui-testing/playwright-tests/Pipelines/pipelines-form-validation.spec.js @@ -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 }); } ); diff --git a/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-regression.spec.js b/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-regression.spec.js index 554868e79b..da4f1738b0 100644 --- a/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-regression.spec.js +++ b/tests/ui-testing/playwright-tests/RegressionSet/Logs/logs-regression.spec.js @@ -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 diff --git a/tests/ui-testing/playwright-tests/utils/global-setup-alpha1.js b/tests/ui-testing/playwright-tests/utils/global-setup-alpha1.js index df88c65810..3adce6937d 100644 --- a/tests/ui-testing/playwright-tests/utils/global-setup-alpha1.js +++ b/tests/ui-testing/playwright-tests/utils/global-setup-alpha1.js @@ -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);