Files
openobserve/tests/ui-testing/playwright-tests/Logs/logsQueryBuilder-chart.spec.js
T
3e89667b13 test: add edition-features documentation validation spec (#12573)
## Summary
- Adds a single GeneralTests spec (`edition-features.spec.js`) that
validates the header edition button, the `EnterpriseUpgradeDialog`,
every enterprise feature card, and the `o2.ws/*` documentation links
each card opens.
- Two tests live in the spec; exactly one runs per environment. The
active edition is detected from the rendered header button label (most
direct signal of what the user sees, tolerates frontend/backend
build-flag mismatches like an OSS frontend served by an ENT backend).
- Pairs with the same-named branch on `openobserve/o2-enterprise` so ENT
CI tree-merges this spec in and runs the ENT variant.

## What the spec validates
- Header button label per edition (`Get OpenObserve Enterprise (Free)`
vs `Edition: Enterprise`)
- Hero panel (title, badge, primary button, features title, Learn More
button)
- 20 enterprise feature cards (presence + external-link icon + BETA/HA
badges)
- Cloud-only `Bring Your Own Bucket` card stays hidden outside Cloud
- Every feature card click opens a popup that lands on the expected
`openobserve.ai` doc page
- Hero external link buttons (`Download Now`/`Learn More` on OSS, `Learn
More` on ENT) — `Get Free License` intentionally not clicked because it
switches the active org to the meta org

## Files
-
`tests/ui-testing/playwright-tests/GeneralTests/edition-features.spec.js`
(new)
- `tests/ui-testing/pages/generalPages/editionFeaturesPage.js` (new —
page object + truth table of slugs / expected doc paths)
- `tests/ui-testing/pages/page-manager.js` (register
`pm.editionFeaturesPage`)

## Test plan
- [x] OSS local run: 1 passed, 1 skipped (~2.0m)
- [x] ENT local run: 1 passed, 1 skipped (~2.0m)
- [x] Sentinel audit passed (POM compliance, no raw selectors in spec,
no hardcoded secrets, no console.log)
- [ ] OSS CI green on this PR
- [ ] ENT CI green on companion PR

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-11 01:14:55 +00:00

564 lines
22 KiB
JavaScript

/**
* Logs Query Builder — Chart Tests
*
* Covers: P0 critical, tab navigation, chart type on tab switch,
* entry conditions, and chart auto-selection.
*
* @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: P0 - Critical Tests (Smoke)
// ============================================================================
test.describe("Logs Query Builder - P0 Critical Tests", () => {
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 Builder test setup completed');
});
test("Build tab opens successfully", {
tag: ['@queryBuilder', '@smoke', '@P0', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Build tab opens successfully');
await pm.logsPage.expectBuildToggleVisible();
await pm.logsPage.clickBuildToggle();
const loaded = await pm.logsPage.waitForBuildTabLoaded();
expect(loaded).toBeTruthy();
testLogger.info('Build tab opens successfully - PASSED');
});
test("Histogram query selects bar chart on Build tab", {
tag: ['@queryBuilder', '@smoke', '@P0', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing histogram query → bar chart');
const histogramQuery = 'SELECT histogram(_timestamp) as time, count(*) as count FROM "e2e_automate" GROUP BY time';
await setupQueryAndSwitchToBuild(pm, page, histogramQuery);
await pm.logsPage.verifyChartTypeSelected('bar');
testLogger.info('Histogram query → bar chart - PASSED');
});
test("CTE query selects table chart on Build tab", {
tag: ['@queryBuilder', '@smoke', '@P0', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing CTE query → table chart');
const cteQuery = 'WITH recent AS (SELECT * FROM "e2e_automate" LIMIT 10) SELECT * FROM recent';
await setupQueryAndSwitchToBuild(pm, page, cteQuery);
await pm.logsPage.verifyChartTypeSelected('table');
testLogger.info('CTE query → table chart - PASSED');
});
test("SELECT star query opens with default histogram/count and bar chart", {
tag: ['@queryBuilder', '@smoke', '@P0', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing SELECT * → default histogram/count → bar chart');
const selectAllQuery = 'SELECT * FROM "e2e_automate"';
await setupQueryAndSwitchToBuild(pm, page, selectAllQuery);
await pm.logsPage.verifyChartTypeSelected('bar');
await pm.logsPage.expectBuilderModeActive();
await pm.logsPage.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
testLogger.info('SELECT * → default histogram/count → bar chart - PASSED');
});
test("SQL mode preserved when toggling to Build tab (not forced ON)", {
tag: ['@queryBuilder', '@smoke', '@P0', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing SQL mode preserved on Build tab switch');
await pm.logsPage.disableSqlModeIfNeeded();
const sqlModeOff = await pm.logsPage.isSqlModeOn();
expect(sqlModeOff).toBe(false);
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
const preserved = await pm.logsPage.verifySqlModePreservedOnBuild(false);
expect(preserved).toBeTruthy();
testLogger.info('SQL mode preserved on Build tab - PASSED');
});
});
// ============================================================================
// Test Suite: P1 - Tab Navigation Tests
// ============================================================================
test.describe("Logs Query Builder - Tab Navigation", () => {
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('Tab Navigation test setup completed');
});
test("Switch from Logs to Build tab", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing switch from Logs to Build tab');
await pm.logsPage.expectBuildToggleVisible();
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
await pm.logsPage.expectBuildTabActive();
testLogger.info('Switch from Logs to Build tab - PASSED');
});
test("Build tab preserves query state on tab switch", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Build tab preserves query state');
await pm.logsPage.enableSqlModeIfNeeded();
await page.waitForLoadState('domcontentloaded');
const testQuery = 'SELECT count(*) as total FROM "e2e_automate"';
await pm.logsPage.setQueryEditorContent(testQuery);
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
await pm.logsPage.clickLogsToggle();
await page.waitForLoadState('domcontentloaded');
// Monaco editor is reinitialised after the Build→Logs tab switch; poll the
// model for the preserved query before reading. Without this, the 500ms
// wait inside clickLogsToggle races against Monaco mounting and
// getQueryEditorText returns "" (CI flake on logsQueryBuilder-chart:165).
await pm.logsPage.waitForQueryEditorValue('e2e_automate');
const queryText = await pm.logsPage.getQueryEditorText();
expect(queryText.toLowerCase()).toContain('e2e_automate');
expect(queryText.trim().length).toBeGreaterThan(0);
testLogger.info('Build tab preserves query state - PASSED');
});
test("SQL Mode auto-enables on Build tab", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing SQL mode behavior on Build tab switch');
await pm.logsPage.disableSqlModeIfNeeded();
const sqlModeOff = await pm.logsPage.isSqlModeOn();
expect(sqlModeOff).toBe(false);
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
const sqlModeAfter = await pm.logsPage.isSqlModeOn();
expect(sqlModeAfter).toBe(false);
testLogger.info('SQL mode preserved (not auto-enabled) on Build tab - PASSED');
});
test("Logs → Build → Logs roundtrip is stable", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Logs → Build → Logs → Build roundtrip');
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
await pm.logsPage.expectBuildTabActive();
await pm.logsPage.clickLogsToggle();
await page.waitForLoadState('domcontentloaded');
await pm.logsPage.expectLogsTableVisible();
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
await pm.logsPage.expectBuildTabActive();
testLogger.info('Roundtrip stable - PASSED');
});
});
// ============================================================================
// Test Suite: P1 - Chart Type Verification on Tab Switch
// ============================================================================
test.describe("Logs Query Builder - Chart Type on Tab Switch", () => {
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('Chart Type on Tab Switch test setup completed');
});
test("Histogram query selects bar chart type", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing histogram → bar chart');
const histogramQuery = 'SELECT histogram(_timestamp) as time, count(*) as count FROM "e2e_automate" GROUP BY time';
await setupQueryAndSwitchToBuild(pm, page, histogramQuery);
await pm.logsPage.verifyChartTypeSelected('bar');
testLogger.info('Histogram → bar chart - PASSED');
});
test("Count-only query selects metric chart type", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing count-only → metric chart');
const countQuery = 'SELECT count(*) as total FROM "e2e_automate"';
await setupQueryAndSwitchToBuild(pm, page, countQuery);
await pm.logsPage.verifyChartTypeSelected('metric');
testLogger.info('Count-only → metric chart - PASSED');
});
test("CTE query selects table chart type", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing CTE → table chart');
const cteQuery = 'WITH recent AS (SELECT * FROM "e2e_automate" LIMIT 10) SELECT * FROM recent';
await setupQueryAndSwitchToBuild(pm, page, cteQuery);
await pm.logsPage.verifyChartTypeSelected('table');
testLogger.info('CTE → table chart - PASSED');
});
test("Subquery selects table chart type", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing subquery → table chart');
const subquery = 'SELECT code, cnt FROM (SELECT code, count(*) as cnt FROM "e2e_automate" GROUP BY code) subq LIMIT 10';
await setupQueryAndSwitchToBuild(pm, page, subquery);
await pm.logsPage.verifyChartTypeSelected('table');
testLogger.info('Subquery → table chart - PASSED');
});
test("SELECT star query selects bar chart type (default histogram/count)", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing SELECT * → bar chart (default histogram/count)');
const selectAllQuery = 'SELECT * FROM "e2e_automate"';
await setupQueryAndSwitchToBuild(pm, page, selectAllQuery);
await pm.logsPage.verifyChartTypeSelected('bar');
await pm.logsPage.expectBuilderModeActive();
await pm.logsPage.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
testLogger.info('SELECT * → bar chart (default histogram/count) - PASSED');
});
test("Histogram with filter populates X-axis, Y-axis, and filter", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing histogram + filter query populates builder fields');
const filterQuery = 'SELECT histogram(_timestamp) as "x_axis_1", count(*) as "y_axis_1" FROM "e2e_automate" WHERE code = \'200\' GROUP BY x_axis_1';
await setupQueryAndSwitchToBuild(pm, page, filterQuery);
await pm.logsPage.verifyChartTypeSelected('bar');
await pm.logsPage.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
testLogger.info('Histogram + filter → builder fields populated - PASSED');
});
});
// ============================================================================
// Test Suite: Entry Conditions (Design Doc Cases 1-9)
// ============================================================================
test.describe("Logs Query Builder - Entry Conditions (Cases 1-9)", () => {
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('Entry Conditions test setup completed');
});
test("Case 1: SQL OFF + Empty → auto mode, histogram/count, bar chart, auto-runs", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Case 1: SQL OFF + Empty');
await pm.logsPage.disableSqlModeIfNeeded();
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
await pm.logsPage.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
await pm.logsPage.verifyChartTypeSelected('bar');
const sqlModeOn = await pm.logsPage.isSqlModeOn();
expect(sqlModeOn).toBe(false);
testLogger.info('Case 1 - PASSED');
});
test("Case 2: SQL OFF + WHERE clause → auto mode, filter parsed from WHERE", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Case 2: SQL OFF + WHERE clause');
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.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
await pm.logsPage.verifyChartTypeSelected('bar');
await pm.logsPage.expectFilterLayoutVisible();
const sqlModeOn = await pm.logsPage.isSqlModeOn();
expect(sqlModeOn).toBe(false);
testLogger.info('Case 2 - PASSED');
});
test("Case 3: SQL ON + Empty → auto mode, default histogram/count, auto-runs", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Case 3: SQL ON + Empty');
await pm.logsPage.enableSqlModeIfNeeded();
await pm.logsPage.setQueryEditorContent('');
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
await pm.logsPage.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
await pm.logsPage.verifyChartTypeSelected('bar');
await pm.logsPage.expectChartOrNoDataAttached();
testLogger.info('Case 3 - PASSED');
});
test("Case 4: SQL ON + simple parseable SQL → auto mode, fields from parsed SQL", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Case 4: SQL ON + simple parseable SQL');
const simpleSQL = '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, simpleSQL);
await pm.logsPage.verifyChartTypeSelected('bar');
await pm.logsPage.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
testLogger.info('Case 4 - PASSED');
});
test("Case 5: SQL ON + complex SQL (CTE) → custom mode, table chart", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Case 5: SQL ON + complex SQL');
const complexSQL = 'WITH recent AS (SELECT * FROM "e2e_automate" LIMIT 10) SELECT * FROM recent';
await setupQueryAndSwitchToBuild(pm, page, complexSQL);
await pm.logsPage.verifyChartTypeSelected('table');
testLogger.info('Case 5 - PASSED');
});
test("Case 6: SQL ON + aggregation only → auto mode, Y-only, metric chart", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Case 6: SQL ON + aggregation only');
const aggOnlySQL = 'SELECT count(*) as total FROM "e2e_automate"';
await setupQueryAndSwitchToBuild(pm, page, aggOnlySQL);
await pm.logsPage.verifyChartTypeSelected('metric');
testLogger.info('Case 6 - PASSED');
});
test("Case 7: SQL ON + >2 GROUP BY → auto mode, table chart", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Case 7: SQL ON + >2 GROUP BY');
const multiGroupSQL = 'SELECT code, method, level, count(*) as "y_axis_1" FROM "e2e_automate" GROUP BY code, method, level';
await setupQueryAndSwitchToBuild(pm, page, multiGroupSQL);
await pm.logsPage.verifyChartTypeSelected('table');
testLogger.info('Case 7 - PASSED');
});
test("Case 8: SQL OFF + multi-stream → only first stream used in builder", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing Case 8: SQL OFF + multi-stream');
await pm.logsPage.disableSqlModeIfNeeded();
await pm.logsPage.clickBuildToggle();
await pm.logsPage.waitForBuildTabLoaded();
await pm.logsPage.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
testLogger.info('Case 8 - PASSED');
});
});
// ============================================================================
// Test Suite: Chart Type Auto-Selection Comprehensive
// ============================================================================
test.describe("Logs Query Builder - Chart Auto-Selection", () => {
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('Chart Auto-Selection test setup completed');
});
test("Custom mode always selects table chart", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing: custom mode → table chart');
const subquery = 'SELECT code, cnt FROM (SELECT code, count(*) as cnt FROM "e2e_automate" GROUP BY code) subq';
await setupQueryAndSwitchToBuild(pm, page, subquery);
await pm.logsPage.verifyChartTypeSelected('table');
testLogger.info('Custom mode → table chart - PASSED');
});
test("Auto mode with Y-axis only selects metric chart", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing: Y-axis only → metric chart');
const yOnlyQuery = 'SELECT avg(code) as "y_axis_1" FROM "e2e_automate"';
await setupQueryAndSwitchToBuild(pm, page, yOnlyQuery);
await pm.logsPage.verifyChartTypeSelected('metric');
testLogger.info('Y-axis only → metric chart - PASSED');
});
test("Auto mode with X + Y selects bar chart (default)", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing: X + Y → bar chart (default)');
const xyQuery = '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, xyQuery);
await pm.logsPage.verifyChartTypeSelected('bar');
await pm.logsPage.expectXAxisLayoutVisible();
await pm.logsPage.expectYAxisLayoutVisible();
testLogger.info('X + Y → bar chart - PASSED');
});
test("Auto mode with >2 GROUP BY selects table chart (useTableChart)", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing: >2 GROUP BY → table chart (useTableChart)');
const tableChartQuery = 'SELECT code, method, level, count(*) as "y_axis_1" FROM "e2e_automate" GROUP BY code, method, level';
await setupQueryAndSwitchToBuild(pm, page, tableChartQuery);
await pm.logsPage.verifyChartTypeSelected('table');
testLogger.info('>2 GROUP BY → table chart - PASSED');
});
test("Multiple Y-axis with X-axis selects bar chart", {
tag: ['@queryBuilder', '@functional', '@P1', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing: multiple Y + X → bar chart');
const multiYQuery = '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';
await setupQueryAndSwitchToBuild(pm, page, multiYQuery);
await pm.logsPage.verifyChartTypeSelected('bar');
testLogger.info('Multiple Y + X → bar chart - PASSED');
});
});