test: fix failing alpha1 e2e tests (#15494)

This commit is contained in:
Shrinath Rao
2026-10-18 12:21:39 +00:00
committed by GitHub
parent 972890f3c1
commit bb0594847b
11 changed files with 73 additions and 30 deletions
@@ -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
+7 -19
View File
@@ -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();
@@ -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}`;
@@ -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);