Files
ktx-akshay 7999fd33f3 test: enhance logs visualization error handling and persistence tests (#13591)
## Summary

Adds **10 E2E tests** covering the two defects fixed in #13244, plus the
`SELECT *` behaviour change that shipped alongside them. Also introduces
per-shard backend configuration so the `SELECT *` tests can actually run
in CI.

## Test coverage

### `logsVisualizePersistence.spec.js` — 7 tests (quick mode **off**)

| # | Test | Defect | Fix code exercised |
|---|---|---|---|
| 1 | Renders without the false required-fields error | A |
`convertPanelData.ts` `!query?.customQuery` |
| 2 | Restores chart type, query and chart after URL refresh (h-bar) | B
| `setupLogsTab` re-run + `isInitialVisualizeRestore` |
| 3 | Re-renders after reload with the default chart type | B |
permanent No-Data half |
| 4 | Restores a non-default chart type (area) after refresh | B |
`restoreVisualizationFromUrlOnLoad()` |
| 5 | Chart still renders after reload with a VRL function applied | B |
`prepareVisualizeMode()` VRL copy |
| 6 | Never shows required-fields error toggling Visualize → Logs →
Visualize | A | manual-toggle rebuild path |
| 7 | No `SELECT *` toast opening Visualize with no stream in the URL |
B | page-load ordering guard |

**Defect A** — false "Please select required fields to render the chart"
on the custom-query Timechart panel.
**Defect B** — URL refresh produced a `SELECT *` toast plus permanent No
Data.

### `logsVisualizeSelectStar.spec.js` — 3 tests (quick mode **on**,
dedicated shard)

| # | Test | Behaviour verified | Call site |
|---|---|---|---|
| 8 | Table chart on `SELECT *` → toast shown **and** chart type reverts
| Table is blocked | `Index.vue:2265` (watcher) |
| 9 | Histogram chart on `SELECT *` → no toast, renders |
Line/bar/area/scatter allowed | entry path |
| 10 | `SELECT *` line chart survives browser refresh → no toast, state
restored | Restore path doesn't re-emit | `Index.vue:2465` (restore) |

## Infrastructure changes

| File | Change | Why |
|---|---|---|
| `ci_matrix.json` | New `Logs-SelectStar` shard with
`quick_mode_enabled: "true"` | `isSelectStarForTable()` only exists when
the server runs with quick mode on |
| `playwright.yml` | Per-shard `ZO_QUICK_MODE_ENABLED` env on `Start
OpenObserve` | The flag is read once at server start — a shard is the
smallest unit that can vary it |
| `build-ci-matrix.js` | Emit `quick_mode_enabled` (defaults `"false"`)
| The field was being silently stripped by the emit whitelist |
| `logsPage.js` | 13 assertion helpers + fixed chart-type detection |
Detection read `data-selected` off the wrong element and matched retired
CSS classes, so it always returned `null` |

All 24 existing shards are unaffected — they omit the field and default
to `false`.

## Notes on test quality

- Every negative assertion ("no toast", "no error") is sequenced after a
deterministic positive end-state, and gated on a precondition so it
cannot pass vacuously.
- Toast absence is proved with a MutationObserver recorder installed
before the navigation under test, since toasts auto-dismiss.
- The quick-mode precondition is a hard assertion, not `test.skip` — a
misconfigured shard fails loudly instead of silently reporting
"skipped".
2026-08-04 12:09:26 +00:00

212 lines
10 KiB
JavaScript

const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
const testLogger = require('../utils/test-logger.js');
const PageManager = require('../../pages/page-manager.js');
const logData = require("../../fixtures/log.json");
const { ingestTestData: _ingestData } = require('../utils/data-ingestion.js');
const { getOrgIdentifier } = require('../utils/cloud-auth.js');
// ============================================================================
// SELECT * handling on the logs Visualize tab — PR #13244 / issue #12897.
//
// The old code blocked SELECT * for EVERY chart type, before the chart type was
// even finalised. The fix replaces that with isSelectStarForTable(): only the
// table chart is blocked (it renders the raw query columns), while histogram-
// based charts (line/bar/area/scatter) render histogram(_timestamp)/count(*)
// and legitimately ignore the SELECT columns.
//
// WHY THIS IS ITS OWN SPEC FILE:
// isSelectStarForTable() short-circuits to false unless the SERVER was started
// with ZO_QUICK_MODE_ENABLED=true. That flag is read once at process start, so
// it cannot vary between tests sharing a backend. CI gives each matrix shard its
// own OpenObserve process, so this file is pinned to its own shard in
// ci-matrix/ci_matrix.json with quick_mode_enabled: "true"; every other shard
// omits the field and keeps the default (false). Keep these tests here rather
// than folding them back into logsVisualizePersistence.spec.js, or they will
// silently skip.
// ============================================================================
// ----- Helpers -----
async function ingestTestData(page) {
await _ingestData(page);
}
// ----- Test Suite -----
test.describe("Logs Visualize SELECT * handling testcases", () => {
test.describe.configure({ mode: 'parallel' });
let pm;
test.beforeEach(async ({ page }, testInfo) => {
testLogger.testStart(testInfo.title, testInfo.file);
await navigateToBase(page);
pm = new PageManager(page);
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await ingestTestData(page);
await page.waitForLoadState('domcontentloaded');
await page.goto(
`${logData.logsUrl}?org_identifier=${getOrgIdentifier()}`
);
await pm.logsPage.selectStream("e2e_automate");
await pm.logsPage.applyQueryAndWaitForSearchResponse();
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
// Fail loudly rather than skip: this shard exists precisely to run with quick
// mode on, so a false here means the shard is misconfigured (or the matrix
// entry was lost) and the coverage has silently evaporated.
expect(
await pm.logsPage.isQuickModeEnabledOnInstance(),
'This spec requires the backend to run with ZO_QUICK_MODE_ENABLED=true — ' +
'check the quick_mode_enabled flag on this shard in ci-matrix/ci_matrix.json'
).toBe(true);
testLogger.info('Test setup completed');
});
test("should block a table chart on a SELECT * query and revert the chart type", {
tag: ['@logs-visualize-selectstar', '@all', '@logs', '@P0']
}, async ({ page }) => {
testLogger.info('Testing SELECT * table guard (toast + chart type revert)');
// 1. SQL mode with an explicit SELECT * — in SQL mode
// getEffectiveVisualizeQuery() returns the raw query, so this is
// deterministically a select-all (quick mode cannot rewrite it).
await pm.logsPage.enableSQLMode();
await pm.logsPage.typeQuery('SELECT * FROM "e2e_automate"');
await pm.logsPage.applyQueryAndWaitForSearchResponse();
// Precondition gate: the editor really holds a select-all. Without this, a
// failure to enter SQL mode would surface as the confusing "expected a toast,
// none emitted" below instead of pointing at the actual cause.
await pm.logsPage.expectQueryEditorContainsText('SELECT * FROM');
// 2. Open Visualize — a histogram chart type is selected by default and must render.
await pm.logsPage.clickVisualizeToggle();
await pm.logsPage.expectVisualizeTabContentVisible();
await pm.logsPage.expectVisualizeChartRendered();
// 3. Record the chart type the panel settled on; the revert must return to it.
const typeBeforeTable = await pm.logsPage.waitForChartTypeStabilized();
expect(typeBeforeTable, 'a chart type should be selected before switching').toBeTruthy();
expect(typeBeforeTable).not.toBe('table');
// 4. Attempt to switch to the table chart with the recorder armed.
await pm.logsPage.startToastRecorder();
await pm.logsPage.selectChartType('table');
// 5. The guard fires: toast shown AND the chart type reverts to the previous one.
await pm.logsPage.expectSelectStarVisualizationToast();
await pm.logsPage.verifyChartTypeSelected('table', false);
await pm.logsPage.verifyChartTypeSelected(typeBeforeTable);
// 6. The revert must leave a WORKING panel, not a blank one — the guard returns
// early after reverting, so this proves the previous chart survived intact.
await pm.logsPage.expectVisualizeChartRendered();
await pm.logsPage.expectNoDashboardErrors();
testLogger.info(`SELECT * table guard fired and reverted to "${typeBeforeTable}"`);
});
test("should allow a histogram chart on a SELECT * query without the SELECT * toast", {
tag: ['@logs-visualize-selectstar', '@all', '@logs', '@P0']
}, async ({ page }) => {
testLogger.info('Testing SELECT * is allowed for histogram-based charts');
// 1. Same SELECT * query that blocks the table chart above.
await pm.logsPage.enableSQLMode();
await pm.logsPage.typeQuery('SELECT * FROM "e2e_automate"');
await pm.logsPage.applyQueryAndWaitForSearchResponse();
// Precondition gate — ESSENTIAL here. This test's payload assertion is the
// ABSENCE of a toast, which passes trivially if the query was never a
// select-all in the first place (e.g. SQL mode failed to engage and quick mode
// rewrote it to SELECT <fields>). Verifying the editor holds a select-all is
// what stops this from becoming a test that can never fail.
await pm.logsPage.expectQueryEditorContainsText('SELECT * FROM');
// 2. Arm the recorder BEFORE entering Visualize — the pre-fix code emitted the
// toast on entry, ahead of any chart-type choice, so recording from here is
// what makes this fail on the unfixed build.
await pm.logsPage.startToastRecorder();
await pm.logsPage.clickVisualizeToggle();
await pm.logsPage.expectVisualizeTabContentVisible();
await pm.logsPage.expectVisualizeChartRendered();
// 3. Explicitly pick a histogram-based chart type and confirm it renders.
await pm.logsPage.selectChartType('line');
await pm.logsPage.verifyChartTypeSelected('line');
await pm.logsPage.expectVisualizeChartRendered();
// 4. No SELECT * toast at any point, and no error panel.
await pm.logsPage.expectNoSelectStarVisualizationToast();
await pm.logsPage.expectNoDashboardErrors();
testLogger.info('SELECT * rendered on a line chart with no toast');
});
test("should keep a SELECT * line chart rendered after a browser refresh without the SELECT * toast", {
tag: ['@logs-visualize-selectstar', '@all', '@logs', '@P0']
}, async ({ page }) => {
testLogger.info('Testing SELECT * + histogram chart survives a URL refresh');
// 1. SELECT * in SQL mode, same as the tests above.
await pm.logsPage.enableSQLMode();
await pm.logsPage.typeQuery('SELECT * FROM "e2e_automate"');
await pm.logsPage.applyQueryAndWaitForSearchResponse();
// Precondition gate — the post-refresh "no toast" assertion below is a negative,
// so it would pass trivially if this were never a select-all query.
await pm.logsPage.expectQueryEditorContainsText('SELECT * FROM');
// 2. Render a histogram-based chart (allowed for SELECT * by the fix).
await pm.logsPage.clickVisualizeToggle();
await pm.logsPage.expectVisualizeTabContentVisible();
await pm.logsPage.expectVisualizeChartRendered();
await pm.logsPage.selectChartType('line');
await pm.logsPage.verifyChartTypeSelected('line');
await pm.logsPage.expectVisualizeChartRendered();
// 3. Gate on the URL sync so the reload actually exercises URL restoration,
// and capture the payload to compare against the restored state.
await pm.logsPage.waitForVisualizeUrlState();
const configBeforeRefresh = await pm.logsPage.getVisualizationDataFromUrl();
expect(configBeforeRefresh, 'visualization_data should be present before refresh').toBeTruthy();
expect(configBeforeRefresh.type).toBe('line');
// 4. Refresh with the recorder armed — the restore path re-runs the
// visualization, which is where a spurious SELECT * toast would surface.
await pm.logsPage.startToastRecorder();
await page.reload();
await page.waitForLoadState('domcontentloaded');
// 5. The chart comes back: same type, actually painted, not the No Data state.
await pm.logsPage.expectVisualizeTabContentVisible();
await pm.logsPage.verifyChartTypeSelected('line');
await pm.logsPage.expectVisualizeChartRendered();
// 6. The SELECT * query itself survived the refresh.
await pm.logsPage.expectQueryEditorContainsText('SELECT * FROM');
// 7. The core assertion: the visualization already rendered successfully, so the
// restore must NOT re-emit the SELECT * message. Only the table chart is
// guarded, and the restored type is line.
await pm.logsPage.expectNoSelectStarVisualizationToast();
await pm.logsPage.expectNoRequiredFieldsError();
await pm.logsPage.expectNoDashboardErrors();
// 8. Panel config survived too — after load the app rewrites visualization_data
// from its restored in-memory panel, so an equal payload proves the restore
// repopulated state rather than the query string merely surviving.
await expect
.poll(async () => (await pm.logsPage.getVisualizationDataFromUrl())?.type, {
timeout: 20000,
message: 'visualization_data was not rewritten after refresh',
})
.toBe('line');
const configAfterRefresh = await pm.logsPage.getVisualizationDataFromUrl();
expect(configAfterRefresh.config).toEqual(configBeforeRefresh.config);
testLogger.info('SELECT * line chart restored after refresh with no toast');
});
});