/** * Logs Query Builder — Editor & Mode Tests * * Covers: builder tab improvement, edge cases, query mode toggle, * SQL mode toggle in builder, search bar editor state, * bare field select defaults, and field list button visibility. * * @tags @queryBuilder @logs @all */ const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js'); const testLogger = require('../utils/test-logger.js'); const PageManager = require('../../pages/page-manager.js'); const { ingestForQueryBuilderTest, setupQueryAndSwitchToBuild, initQueryBuilderTest, } = require('../utils/queryBuilder-helpers.js'); // ============================================================================ // Test Suite: P1 - Builder Tab Improvement // ============================================================================ test.describe("Logs Query Builder - Builder Tab Improvement", () => { test.describe.configure({ mode: 'parallel' }); let pm; test.beforeAll(async ({ request }) => { await ingestForQueryBuilderTest(request); }); test.beforeEach(async ({ page }, testInfo) => { testLogger.testStart(testInfo.title, testInfo.file); await navigateToBase(page); pm = new PageManager(page); await initQueryBuilderTest(page, pm); testLogger.info('Builder Tab Improvement test setup completed'); }); test("SQL OFF + empty query → Build tab opens with default fields and bar chart", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing Case 1: SQL OFF + empty → default histogram/count'); await pm.logsPage.disableSqlModeIfNeeded(); await pm.logsPage.clickBuildToggle(); await pm.logsPage.waitForBuildTabLoaded(); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.verifyChartTypeSelected('bar'); const sqlModeOn = await pm.logsPage.isSqlModeOn(); expect(sqlModeOn).toBe(false); testLogger.info('Case 1: SQL OFF + empty → default fields + bar - PASSED'); }); test("SQL OFF + WHERE clause → Build tab parses filter into builder", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing Case 2: SQL OFF + WHERE clause → filter parsed'); await pm.logsPage.disableSqlModeIfNeeded(); await page.waitForLoadState('domcontentloaded'); await pm.logsPage.setQueryEditorContent("code = '200'"); await pm.logsPage.clickBuildToggle(); await pm.logsPage.waitForBuildTabLoaded(); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.verifyChartTypeSelected('bar'); const sqlModeOn = await pm.logsPage.isSqlModeOn(); expect(sqlModeOn).toBe(false); testLogger.info('Case 2: SQL OFF + WHERE → filter parsed + bar - PASSED'); }); test("SQL ON + empty query → Build tab opens with default fields and bar chart", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing Case 3: SQL ON + empty → default histogram/count'); await pm.logsPage.enableSqlModeIfNeeded(); await pm.logsPage.setQueryEditorContent(''); await pm.logsPage.clickBuildToggle(); await pm.logsPage.waitForBuildTabLoaded(); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.verifyChartTypeSelected('bar'); testLogger.info('Case 3: SQL ON + empty → default fields + bar - PASSED'); }); test("Query mode toggle: Auto → Custom shows SQL editor", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing query mode toggle: Auto → Custom'); const histogramQuery = 'SELECT histogram(_timestamp) as time, count(*) as count FROM "e2e_automate" GROUP BY time'; await setupQueryAndSwitchToBuild(pm, page, histogramQuery); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.clickCustomQueryType(); await pm.logsPage.expectCustomModeActive(); testLogger.info('Query mode toggle: Auto → Custom - PASSED'); }); test("SQL ON + multiple aggregations → builder shows multiple Y-axis fields", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing multiple aggregations → multiple Y-axis'); const multiAggQuery = 'SELECT histogram(_timestamp) as "x_axis_1", count(_timestamp) as "y_axis_1", avg(code) as "y_axis_2" FROM "e2e_automate" GROUP BY x_axis_1 ORDER BY x_axis_1 ASC'; await setupQueryAndSwitchToBuild(pm, page, multiAggQuery); await pm.logsPage.verifyChartTypeSelected('bar'); await pm.logsPage.expectXAxisLayoutVisible(); await pm.logsPage.expectYAxisLayoutVisible(); testLogger.info('Multiple aggregations → multiple Y-axis - PASSED'); }); test("SQL ON + breakdown field → builder shows breakdown section", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing breakdown field populated in builder'); const breakdownQuery = 'SELECT histogram(_timestamp) as "x_axis_1", count(_timestamp) as "y_axis_1", code as "z_axis_1" FROM "e2e_automate" GROUP BY x_axis_1, z_axis_1 ORDER BY x_axis_1 ASC'; await setupQueryAndSwitchToBuild(pm, page, breakdownQuery); await pm.logsPage.verifyChartTypeSelected('bar'); await pm.logsPage.expectXAxisLayoutVisible(); await pm.logsPage.expectYAxisLayoutVisible(); await pm.logsPage.expectBreakdownLayoutVisible(); testLogger.info('Breakdown field populated - PASSED'); }); }); // ============================================================================ // Test Suite: P2 - Edge Cases // ============================================================================ test.describe("Logs Query Builder - Edge Cases", () => { test.describe.configure({ mode: 'parallel' }); let pm; test.beforeAll(async ({ request }) => { await ingestForQueryBuilderTest(request); }); test.beforeEach(async ({ page }, testInfo) => { testLogger.testStart(testInfo.title, testInfo.file); await navigateToBase(page); pm = new PageManager(page); await initQueryBuilderTest(page, pm); testLogger.info('Edge Cases test setup completed'); }); test("SQL ON mode preserved when toggling to Build tab", { tag: ['@queryBuilder', '@edge', '@P2', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SQL ON preserved on Build tab switch'); await pm.logsPage.enableSqlModeIfNeeded(); await pm.logsPage.clickBuildToggle(); await pm.logsPage.waitForBuildTabLoaded(); const preserved = await pm.logsPage.verifySqlModePreservedOnBuild(true); expect(preserved).toBeTruthy(); testLogger.info('SQL ON preserved on Build tab - PASSED'); }); test("Window function query selects table chart on Build tab", { tag: ['@queryBuilder', '@edge', '@P2', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing window function → table chart'); const windowQuery = 'SELECT *, ROW_NUMBER() OVER (ORDER BY _timestamp DESC) as rn FROM "e2e_automate" LIMIT 10'; await setupQueryAndSwitchToBuild(pm, page, windowQuery); await pm.logsPage.expectDashboardPanelTableVisible(); await pm.logsPage.verifyChartTypeSelected('table'); testLogger.info('Window function → table chart - PASSED'); }); test("Case 7: SQL ON + >2 GROUP BY → table chart", { tag: ['@queryBuilder', '@edge', '@P2', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing Case 7: >2 GROUP BY → table chart'); const multiGroupByQuery = 'SELECT code, method, level, count(*) as "y_axis_1" FROM "e2e_automate" GROUP BY code, method, level'; await setupQueryAndSwitchToBuild(pm, page, multiGroupByQuery); await pm.logsPage.expectDashboardPanelTableVisible(); await pm.logsPage.verifyChartTypeSelected('table'); testLogger.info('Case 7: >2 GROUP BY → table chart - PASSED'); }); test("Case 9: SQL ON + multi-stream JOIN → builder mode, bar chart", { tag: ['@queryBuilder', '@edge', '@P2', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing Case 9: multi-stream JOIN → builder mode → bar'); const joinQuery = 'SELECT a._timestamp, b._timestamp FROM "e2e_automate" a JOIN "e2e_automate" b ON a._timestamp = b._timestamp LIMIT 10'; await setupQueryAndSwitchToBuild(pm, page, joinQuery); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.verifyChartTypeSelected('bar'); testLogger.info('Case 9: multi-stream JOIN → builder mode, bar chart - PASSED'); }); }); // ============================================================================ // Test Suite: Query Mode Toggle (Auto ↔ Custom) While in Builder // ============================================================================ test.describe("Logs Query Builder - Query Mode Toggle", () => { test.describe.configure({ mode: 'parallel' }); let pm; test.beforeAll(async ({ request }) => { await ingestForQueryBuilderTest(request); }); test.beforeEach(async ({ page }, testInfo) => { testLogger.testStart(testInfo.title, testInfo.file); await navigateToBase(page); pm = new PageManager(page); await initQueryBuilderTest(page, pm); testLogger.info('Query Mode Toggle test setup completed'); }); test("SQL ON: Auto → Custom shows full generated SQL in editor", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing Auto → Custom (SQL ON)'); const histogramQuery = 'SELECT histogram(_timestamp) as "x_axis_1", count(_timestamp) as "y_axis_1" FROM "e2e_automate" GROUP BY x_axis_1'; await setupQueryAndSwitchToBuild(pm, page, histogramQuery); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.clickCustomQueryType(); await pm.logsPage.expectCustomModeActive(); const editorText = await pm.logsPage.getQueryEditorText(); expect(editorText.toLowerCase()).toContain('select'); expect(editorText.toLowerCase()).toContain('from'); testLogger.info('Auto → Custom (SQL ON) - PASSED'); }); test("SQL OFF: Auto → Custom shows WHERE clause in editor", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing Auto → Custom (SQL OFF)'); await pm.logsPage.disableSqlModeIfNeeded(); await pm.logsPage.setQueryEditorContent("code = '200'"); await pm.logsPage.clickBuildToggle(); await pm.logsPage.waitForBuildTabLoaded(); await pm.logsPage.clickCustomQueryType(); await pm.logsPage.expectCustomModeActive(); const editorText = await pm.logsPage.getQueryEditorText(); expect(editorText.toLowerCase()).not.toContain('select'); testLogger.info('Auto → Custom (SQL OFF) - PASSED'); }); }); // ============================================================================ // Test Suite: SQL Mode Toggle While in Builder // ============================================================================ test.describe("Logs Query Builder - SQL Mode Toggle in Builder", () => { test.describe.configure({ mode: 'parallel' }); let pm; test.beforeAll(async ({ request }) => { await ingestForQueryBuilderTest(request); }); test.beforeEach(async ({ page }, testInfo) => { testLogger.testStart(testInfo.title, testInfo.file); await navigateToBase(page); pm = new PageManager(page); await initQueryBuilderTest(page, pm); testLogger.info('SQL Mode Toggle in Builder test setup completed'); }); test("SQL OFF → ON in builder: search bar shows full SQL", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SQL OFF → ON in builder'); await pm.logsPage.disableSqlModeIfNeeded(); await pm.logsPage.clickBuildToggle(); await pm.logsPage.waitForBuildTabLoaded(); await pm.logsPage.enableSqlModeIfNeeded(); await page.waitForLoadState('domcontentloaded'); const editorText = await pm.logsPage.getQueryEditorText(); expect(editorText.toLowerCase()).toContain('select'); expect(editorText.toLowerCase()).toContain('from'); testLogger.info('SQL OFF → ON: full SQL shown - PASSED'); }); test("SQL ON → OFF in builder: search bar shows WHERE clause only", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SQL ON → OFF in builder'); const filterQuery = 'SELECT histogram(_timestamp) as "x_axis_1", count(_timestamp) as "y_axis_1" FROM "e2e_automate" WHERE code = \'200\' GROUP BY x_axis_1'; await setupQueryAndSwitchToBuild(pm, page, filterQuery); await pm.logsPage.disableSqlModeIfNeeded(); await page.waitForLoadState('domcontentloaded'); const editorText = await pm.logsPage.getQueryEditorText(); // When SQL mode is turned OFF in the build tab, `onBuildQueryGenerated` // syncs ONLY the extracted WHERE clause condition (no `WHERE` keyword) into // the search bar — matching the search bar's filter-only mode. Verify the // filter content (`code` field) is retained. expect(editorText.toLowerCase()).toContain('code'); testLogger.info('SQL ON → OFF: WHERE clause filter preserved in builder-generated SQL - PASSED'); }); }); // ============================================================================ // Test Suite: Search Bar Editor State in Builder // ============================================================================ test.describe("Logs Query Builder - Search Bar Editor State", () => { test.describe.configure({ mode: 'parallel' }); let pm; test.beforeAll(async ({ request }) => { await ingestForQueryBuilderTest(request); }); test.beforeEach(async ({ page }, testInfo) => { testLogger.testStart(testInfo.title, testInfo.file); await navigateToBase(page); pm = new PageManager(page); await initQueryBuilderTest(page, pm); testLogger.info('Search Bar Editor State test setup completed'); }); test("SQL OFF + Auto mode: editor disabled with WHERE clause", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SQL OFF + Auto: editor disabled'); await pm.logsPage.disableSqlModeIfNeeded(); await pm.logsPage.setQueryEditorContent("code = '200'"); await pm.logsPage.clickBuildToggle(); await pm.logsPage.waitForBuildTabLoaded(); await pm.logsPage.expectMonacoEditorAreaVisible(); await pm.logsPage.expectBuilderModeActive(); testLogger.info('SQL OFF + Auto: editor disabled - PASSED'); }); test("SQL ON + Auto mode: editor disabled with full generated SQL", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SQL ON + Auto: editor disabled'); const histogramQuery = 'SELECT histogram(_timestamp) as "x_axis_1", count(_timestamp) as "y_axis_1" FROM "e2e_automate" GROUP BY x_axis_1'; await setupQueryAndSwitchToBuild(pm, page, histogramQuery); // Wait for builder toggle to be active AND for the editor to actually contain SQL. // expectBuilderModeActive() passes as soon as the toggle state is set (from the // PanelEditor immediate watcher), but makeAutoSQLQuery() needs the updateGroupedFields // API call to finish first — that takes longer in CI. await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.expectQueryEditorPopulated(); const editorText = await pm.logsPage.getQueryEditorText(); expect(editorText.toLowerCase()).toContain('select'); expect(editorText.toLowerCase()).toContain('from'); testLogger.info('SQL ON + Auto: editor disabled - PASSED'); }); test("SQL ON + Custom mode: editor editable with full SQL", { tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SQL ON + Custom: editor editable'); const histogramQuery = 'SELECT histogram(_timestamp) as "x_axis_1", count(_timestamp) as "y_axis_1" FROM "e2e_automate" GROUP BY x_axis_1'; await setupQueryAndSwitchToBuild(pm, page, histogramQuery); // Wait for builder toggle AND editor content before switching to Custom mode. // onBuildModeToggle reads queries[0].query to populate the editor; that value // is set by makeAutoSQLQuery() which needs the updateGroupedFields API call first. await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.expectQueryEditorPopulated(); await pm.logsPage.clickCustomQueryType(); await pm.logsPage.expectCustomModeActive(); const editorText = await pm.logsPage.getQueryEditorText(); expect(editorText.toLowerCase()).toContain('select'); await pm.logsPage.setQueryEditorContent('SELECT count(*) FROM "e2e_automate"'); const newText = await pm.logsPage.getQueryEditorText(); expect(newText.toLowerCase()).toContain('count'); testLogger.info('SQL ON + Custom: editor editable - PASSED'); }); }); // ============================================================================ // Test Suite: Bare Field Select → Default histogram/count (Case 3a) // ============================================================================ test.describe("Logs Query Builder — Bare Field Select defaults (Case 3a)", () => { test.describe.configure({ mode: 'parallel' }); let pm; test.beforeAll(async ({ request }) => { await ingestForQueryBuilderTest(request); }); test.beforeEach(async ({ page }, testInfo) => { testLogger.testStart(testInfo.title, testInfo.file); await navigateToBase(page); pm = new PageManager(page); await initQueryBuilderTest(page, pm); testLogger.info('Bare field select test setup completed'); }); test("Single bare field gets default histogram/count on build toggle", { tag: ['@queryBuilder', '@bareFieldSelect', '@P0', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SELECT single_field → default histogram/count'); await setupQueryAndSwitchToBuild(pm, page, 'SELECT kubernetes_container_name FROM "e2e_automate"'); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.expectXAxisHasItems(); await pm.logsPage.expectYAxisHasItems(); await pm.logsPage.verifyChartTypeSelected('bar'); testLogger.info('Single bare field → default histogram/count - PASSED'); }); test("Multiple bare fields get default histogram/count on build toggle", { tag: ['@queryBuilder', '@bareFieldSelect', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SELECT field1, field2 → default histogram/count'); await setupQueryAndSwitchToBuild(pm, page, 'SELECT kubernetes_container_name, kubernetes_host FROM "e2e_automate"'); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.expectXAxisHasItems(); await pm.logsPage.expectYAxisHasItems(); await pm.logsPage.verifyChartTypeSelected('bar'); testLogger.info('Multiple bare fields → default histogram/count - PASSED'); }); test("Bare field with WHERE clause preserves filter and uses defaults", { tag: ['@queryBuilder', '@bareFieldSelect', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing SELECT field WHERE condition → defaults + filter preserved'); await setupQueryAndSwitchToBuild(pm, page, "SELECT kubernetes_container_name FROM \"e2e_automate\" WHERE kubernetes_host = 'test'"); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.expectXAxisHasItems(); await pm.logsPage.expectYAxisHasItems(); await pm.logsPage.verifyChartTypeSelected('bar'); testLogger.info('Bare field with WHERE → defaults + filter - PASSED'); }); test("Query with aggregation does NOT use defaults (parsed normally)", { tag: ['@queryBuilder', '@bareFieldSelect', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Testing query with aggregation → parsed normally'); const aggQuery = 'SELECT kubernetes_container_name, count(_timestamp) as "y_axis_1" FROM "e2e_automate" GROUP BY kubernetes_container_name'; await setupQueryAndSwitchToBuild(pm, page, aggQuery); await pm.logsPage.expectBuilderModeActive(); const xCount = await pm.logsPage.expectXAxisHasItems(); expect(xCount).toBeGreaterThanOrEqual(1); const yCount = await pm.logsPage.expectYAxisHasItems(); expect(yCount).toBeGreaterThanOrEqual(1); testLogger.info('Query with aggregation → parsed normally - PASSED'); }); }); // ============================================================================ // Test Suite: FieldList Button Visibility // ============================================================================ test.describe("Logs Query Builder — FieldList button visibility", () => { test.describe.configure({ mode: 'parallel' }); let pm; test.beforeAll(async ({ request }) => { await ingestForQueryBuilderTest(request); }); test.beforeEach(async ({ page }, testInfo) => { testLogger.testStart(testInfo.title, testInfo.file); await navigateToBase(page); pm = new PageManager(page); await initQueryBuilderTest(page, pm); testLogger.info('FieldList button test setup completed'); }); test("Search filter does not break button visibility in builder mode", { tag: ['@queryBuilder', '@fieldListButtons', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Verifying search filter does not break buttons'); await setupQueryAndSwitchToBuild(pm, page, 'SELECT * FROM "e2e_automate"'); await pm.logsPage.expectBuilderModeActive(); await pm.logsPage.searchFieldInBuilder('kubernetes'); const visibleXCount = await pm.logsPage.getAddXButtonCount(); expect(visibleXCount).toBeGreaterThan(0); testLogger.info(`After filter: ${visibleXCount} +X buttons visible`); await pm.logsPage.searchFieldInBuilder(''); const afterClearCount = await pm.logsPage.getAddXButtonCount(); expect(afterClearCount).toBeGreaterThan(0); testLogger.info(`After clear: ${afterClearCount} +X buttons visible - PASSED`); }); test("Custom mode: buttons visible on custom/VRL fields after search", { tag: ['@queryBuilder', '@fieldListButtons', '@P1', '@all', '@logs'] }, async ({ page }) => { testLogger.info('Verifying buttons in custom mode with search'); await setupQueryAndSwitchToBuild(pm, page, 'SELECT kubernetes_container_name, kubernetes_host FROM "e2e_automate"'); await pm.logsPage.clickCustomQueryType(); await pm.logsPage.searchFieldInBuilder('axis'); const buttonCount = await pm.logsPage.getAddXButtonCount(); testLogger.info(`Custom mode + search: ${buttonCount} +X buttons`); expect(buttonCount).toBeGreaterThan(0); testLogger.info('Custom mode buttons with search - PASSED'); }); });