Files
openobserve/tests/ui-testing/playwright-tests/Logs/logsquickmode.spec.js
T
9c32b03b34 test: stabilize alpha1 Logs-Core shard on cloud (quick-mode detection + search-toolbar sync + scheduler-list hidden-button recovery) (#13240)
## What

Stabilizes the **Logs-Core** shard of the alpha1 cloud E2E suite to
green, running
**fully parallel** (`--workers=5`). Test-infra + page-object changes
only; **no product
code** (7 files under `tests/ui-testing/`). 

## Root causes & fixes

### Quick mode / interesting fields (7 tests)
- **`enableQuickModeIfDisabled` treated the wrong element as the
quick-mode signal.** It
returned early whenever the field-list **user-defined-schema toggle
group** was visible —
but `FieldListPagination` renders that group whenever a stream exposes a
UDS (always true
on cloud), independent of `searchObj.meta.quickMode`. The per-field
interesting (star)
buttons are gated purely on `quickMode`, so they never rendered and
every
`ensureFieldIsInteresting` call timed out. Now verifies the **actual**
Quick Mode switch
state via the utilities menu and confirms by waiting for a per-field
interesting button.
- **`sqlModeOrderBy` hung on `waitForLoadState('networkidle')`** —
streaming keeps the
network busy so it never settles; switched to `domcontentloaded` (the
editor assertion is
  the real readiness gate).

### Search-toolbar synchronization (logspage / histogram flakes)
- **`clickRefreshButton` / `clickRefresh` clicked the refresh button
mid-state-swap.** The
Run-query button swaps to a "Cancel query" variant (and briefly
detaches/disables) while a
prior/auto search is in flight; clicking then cancelled that search or
hit a
  not-visible/detached node (`element is not visible`), and a subsequent
`waitForResponse('/_search')` never fired (60s timeout). Both now wait
for the run-mode
button to be **visible AND idle** (not Cancel, not disabled/`aria-busy`)
before clicking —
  no `force`.
- **`clickLiveMode5Sec` + `clickRelative6WeeksButton` popovers shifted
mid-click.** The
refresh-interval dropdown and the date picker are popovers anchored to
the toolbar; while a
search is in flight the toolbar reflows and the popover repositions, so
the target never
reaches Playwright's "stable" state (~45s click timeout) or the click
misses (date range
  stayed "Past 15 Minutes"). Both now wait for the toolbar to settle
(`_waitForQueryButtonIdle`) first; the date preset additionally verifies
its trigger label
  and retries once.

### Histogram error-handling
- **Query-propagation race** — `clearAndFillQueryEditor` fills Monaco
but the value reaches
the store on a debounce, so `clickRefresh()` could run the **previous**
query and the
no-events/error state never appeared. Now waits for the Monaco model to
reflect the new
  query (`waitForQueryEditorValue`) before running.

### Logs-table field management
- **Fixed `waitForTimeout(2000)` in `beforeEach` →
`waitForFieldListReady()`** — the
expandable field rows only render once the stream's fields have indexed
**values**, and
cloud indexing after ingestion lags under parallel load, so the sidebar
was empty
(`fieldCount === 0`, "field expand button not found"). Gates on the
data-backed field list
  (60s, matching the global-setup indexing budget).
- **Severity-colors** read status bars after a fixed 3s → poll until
they render.
- **Case-insensitivity** counted matching fields after a fixed 500ms →
`expect.poll`.
- **Persist-after-refresh (app timing)** — the added column survives
reload only via the
`logFilterField` localStorage entry, written **after** the column
renders; reloading before
that commit lost it (now waits for the localStorage write). The restore
is then
intermittently slow/dropped on the first reload under cloud load, so it
reloads up to 2×
  and polls for the restored column.

### jobSearch — scheduler-list hidden button (app defect, worked around)
- **App defect:** the scheduler list renders under
`v-show="showSearchScheduler"`, and the
logs page **resets that flag whenever the URL query loses the
`action=search_scheduler`
param** (`updateUrlQueryParams` strips it after a search). The "Get
Jobs" button is then in
the DOM but `display:none` and never recovers — the whole jobSearch CRUD
suite timed out.
`_getJobRowIndex` now **re-navigates to the scheduler-list URL**
(restores the param →
re-shows the list) and retries; validated firing and recovering live on
alpha. Written up
  for a frontend fix (`fix/search-scheduler-job-issue`).
- **`validateAddJob` "Job added successfully" toast made best-effort** —
it's transient and
`searchSchedulerSubmit` already awaited the POST (returned the jobId),
so the toast can
auto-dismiss before the assertion runs; creation is already confirmed by
the jobId + the
scheduler-list lookup. Same re-open recovery applied to its Get-Jobs
click.

## Scope / safety

- **7 files:** `Logs` page-objects (`logsPage`, `logsQueryPage`,
`logsPageEP`,
`jobScheduler`) + Logs specs (`logshistogram`, `logsquickmode`,
`logstable`). **No product
  code.**
- **No tests deleted, no `.only`, no unconditional skips, no assertions
removed.**
- Every fix replaces a fixed sleep / wrong assumption with a
deterministic wait on the real
  condition — no `waitForTimeout`, no `force` in the touched paths.

## Validation

- Local on cloud alpha: `logsquickmode` 8/8, `logshistogram` 5/5,
`logspage` 23/23,
`logstable` field-management + persist (`--repeat-each` clean),
jobSearch scheduler-list
  recovery observed firing and recovering live.
- alpha1 Logs-Core CI: green —
https://github.com/openobserve/o2-enterprise/actions/runs/29471282407


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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
2026-07-17 07:30:01 +00:00

288 lines
13 KiB
JavaScript

const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
const PageManager = require('../../pages/page-manager.js');
const logData = require("../../fixtures/log.json");
const logsdata = require("../../../test-data/logs_data.json");
const testLogger = require('../utils/test-logger.js');
// Utility Functions
function removeUTFCharacters(text) {
return text.replace(/[^\x00-\x7F]/g, " ");
}
async function applyQueryButton(page) {
testLogger.step('Applying query button');
const pm = new PageManager(page);
await pm.logsPage.runQueryAndWaitForResults();
// Accept any definite post-query state: results table, no-events, or error.
// Tests that require actual rows call waitForSearchResults() in their body.
await pm.logsPage.waitForSearchResultOrEmpty();
testLogger.debug('Query applied and page reached a definite state');
}
test.describe("Logs Quickmode testcases", () => {
test.describe.configure({ mode: 'parallel' });
let pm; // Page Manager instance
test.beforeEach(async ({ page }, testInfo) => {
// Initialize test setup
testLogger.testStart(testInfo.title, testInfo.file);
// Navigate to base URL with authentication
await navigateToBase(page);
pm = new PageManager(page);
// Navigate to logs page
const logsUrl = `${logData.logsUrl}?org_identifier=${process.env["ORGNAME"]}`;
testLogger.navigation('Navigating to logs page', { url: logsUrl });
await page.goto(logsUrl);
await page.waitForLoadState('domcontentloaded');
// Simple setup - select stream
try {
await pm.logsPage.selectStream("e2e_automate");
testLogger.info('Stream selected successfully');
} catch (error) {
testLogger.warn('Stream selection failed, continuing test', { error: error.message });
}
// Apply query to load data
await applyQueryButton(page);
// Enable quick mode toggle if it's not already enabled - using page object method
try {
await pm.logsPage.enableQuickModeIfDisabled();
testLogger.info('Quick mode enabled');
} catch (error) {
testLogger.warn('Quick mode toggle failed', { error: error.message });
}
// Click all fields button using page object
try {
await pm.logsPage.clickAllFieldsButton();
testLogger.info('All fields button clicked');
} catch (error) {
testLogger.warn('All fields button click failed', { error: error.message });
}
// Reset SQL mode to OFF so that clickSQLModeToggle reliably turns it ON.
// If a previous test (or localStorage restore) left the editor in SQL mode,
// clickSQLModeToggle would clear the editor instead of building a SELECT query.
try {
await pm.logsPage.disableSqlModeIfNeeded();
testLogger.info('SQL mode reset to OFF');
} catch (error) {
testLogger.warn('SQL mode reset failed, continuing', { error: error.message });
}
testLogger.info('Test setup completed');
});
test.afterEach(async ({ page }) => {
try {
// await pm.commonActions.flipStreaming();
testLogger.info('Streaming flipped after test');
} catch (error) {
testLogger.warn('Streaming flip failed', { error: error.message });
}
});
test("should click on interesting fields icon and display query in editor", {
tag: ['@interestingFieldsLogs', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing interesting fields and query editor');
await pm.logsPage.fillIndexFieldSearchInput("kubernetes_pod_id");
// Use ensureFieldIsInteresting so that if the field is already starred (e.g. from
// localStorage from a previous run) we don't accidentally remove it via toggle.
await pm.logsPage.ensureFieldIsInteresting("kubernetes_pod_id");
await pm.logsPage.clickSQLModeToggle();
await pm.logsPage.waitForQueryEditorTextbox();
await pm.logsPage.expectInterestingFieldInEditor("_timestamp,kubernetes_pod_id");
testLogger.info('Interesting fields and query editor test completed');
});
test("should display quick mode toggle button", {
tag: ['@quickModeLogs', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing quick mode toggle button visibility');
await pm.logsPage.expectQuickModeToggleVisible();
testLogger.info('Quick mode toggle button visible');
});
// Bug #10821 test moved to RegressionSet/logs-regression-bugs.spec.js
test("should click on interesting fields icon in histogram mode and run query", {
tag: ['@interestingFieldsHistogramModeLogs', '@histogram', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing interesting fields in histogram mode');
await pm.logsPage.fillIndexFieldSearchInput("kubernetes_pod_id");
testLogger.info('Validated: field search input filtered to "kubernetes_pod_id"');
await pm.logsPage.ensureFieldIsInteresting("kubernetes_pod_id");
testLogger.info('Validated: "kubernetes_pod_id" marked as an interesting field via sidebar button');
await pm.logsPage.clickSearchBarRefreshButton();
testLogger.info('Validated: search refresh triggered with interesting field selection active');
// Marking a field "interesting" adds it to the query, not as its own table
// column. In the default (non-SQL) view the table renders the FTS body
// column (or the generic "source" column), so assert that results rendered
// rather than a dedicated kubernetes_pod_id column.
await pm.logsPage.expectLogTableColumnSourceVisible();
testLogger.info('Validated: results rendered in histogram mode with interesting field active');
testLogger.info('Interesting fields histogram mode test completed');
});
test("should display error on entering random text in histogram mode when quick mode is on", {
tag: ['@errorHandlingHistogramModeLogs', '@histogram', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing error handling with random text in histogram mode');
// Enable SQL mode so "oooo" is invalid SQL and triggers a backend parse error
// (in quick/FTS mode "oooo" is a valid full-text search term that returns empty results, not an error)
await pm.logsPage.enableSqlModeIfNeeded();
// Replace the entire editor content with "oooo" (typeQuery does select-all + fill,
// ensuring the full SQL query is exactly "oooo" — not appended to an existing valid query
// that DataFusion could interpret as a table alias and return results instead of an error).
await pm.logsPage.waitForQueryEditorTextbox();
await pm.logsPage.typeQuery("oooo");
// Wait for the editor model to reflect the new value before refreshing
await pm.logsPage.expectQueryEditorContainsText("oooo");
await pm.logsPage.waitForSearchBarRefreshButton();
await pm.logsPage.clickSearchBarRefreshButton();
// expectErrorMessageVisible auto-waits via expect().toBeVisible()
await pm.logsPage.expectErrorMessageVisible();
testLogger.info('Error handling histogram mode test completed');
});
test("should display selected interestesing field and order by - as default in editor", {
tag: ['@interestingFieldsSqlModeLogs', '@sqlMode', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing interesting fields SQL mode default behavior');
await pm.logsPage.fillIndexFieldSearchInput("kubernetes_pod_id");
await pm.logsPage.ensureFieldIsInteresting("kubernetes_pod_id");
await pm.logsPage.clickSQLModeToggle();
await pm.logsPage.waitForQueryEditorTextbox();
await pm.logsPage.expectQueryEditorVisible();
testLogger.info('Interesting fields SQL mode test completed');
});
test("should adding/removing interesting field removes it from editor and results too", {
tag: ['@interestingFieldsCRUD', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing interesting fields add/remove functionality');
await pm.logsPage.fillIndexFieldSearchInput("kubernetes_container_name");
await pm.logsPage.ensureFieldIsInteresting("kubernetes_container_name");
await pm.logsPage.fillIndexFieldSearchInput("level");
await pm.logsPage.ensureFieldIsInteresting("level");
await pm.logsPage.clickSQLModeToggle();
await pm.logsPage.waitForQueryEditorTextbox();
await pm.logsPage.runQueryAfterModeChange();
// Interesting fields drive the SQL SELECT but are not pinned as table columns,
// so the custom-query view renders the generic "source" column. (Helper is
// mode-aware: passes on source OR any rendered column.)
await pm.logsPage.expectLogTableColumnSourceVisible();
await pm.logsPage.clickInterestingFieldButton("level");
await pm.logsPage.expectQueryEditorNotContainsText("level");
await pm.logsPage.clickSearchBarRefreshButton();
await pm.logsPage.expectLogTableColumnSourceVisible();
// The "source" cell renders the full row JSON; assert its rendered text is not
// the literal header label "source" (i.e. real data is present).
await pm.logsPage.expectLogTableColumnSourceNotHaveText("source");
testLogger.info('Interesting fields add/remove test completed');
});
test("should display order by in sql mode by default even after page reload", {
tag: ['@sqlModeOrderBy', '@sqlMode', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing SQL mode order by persistence after page reload');
await pm.logsPage.fillIndexFieldSearchInput("kubernetes_pod_id");
await pm.logsPage.ensureFieldIsInteresting("kubernetes_pod_id");
await pm.logsPage.clickSQLModeToggle();
await pm.logsPage.clickSearchBarRefreshButton();
await pm.logsPage.page.reload();
// Use domcontentloaded, not networkidle: with streaming enabled (cloud/alpha) the
// long-lived SSE/search connections keep the network busy, so networkidle never
// settles and times out. expectQueryEditorContainsText below already waits for the
// Monaco editor to mount and hydrate its model value, so it is the real readiness gate.
await pm.logsPage.page.waitForLoadState('domcontentloaded');
await pm.logsPage.expectQueryEditorContainsText('SELECT _timestamp,kubernetes_pod_id FROM "e2e_automate"');
testLogger.info('SQL mode order by persistence test completed');
});
test("should display results without adding timestamp in quick mode", {
tag: ['@quickModeResults', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing quick mode results without timestamp');
await pm.logsPage.fillIndexFieldSearchInput("kubernetes_pod_id");
testLogger.info('Validated: field search input filtered to "kubernetes_pod_id"');
await pm.logsPage.ensureFieldIsInteresting("kubernetes_pod_id");
testLogger.info('Validated: "kubernetes_pod_id" marked as an interesting field');
await pm.logsPage.clickSQLModeToggle();
testLogger.info('Validated: switched to SQL mode');
await pm.logsPage.waitForQueryEditorTextbox();
await pm.logsPage.runQueryAfterModeChange();
// The interesting field drives the SQL SELECT but is not pinned as a column,
// so the custom-query view renders the generic "source" column. (Helper is
// mode-aware: passes on source OR any rendered column.)
await pm.logsPage.expectLogTableColumnSourceVisible();
testLogger.info('Validated: results rendered — query ran without auto-injecting _timestamp in quick mode');
testLogger.info('Quick mode results test completed');
});
test("should click timestamp field and then search for kubernetes_pod_id field", {
tag: ['@fieldInteraction', '@all', '@logs']
}, async ({ page }) => {
testLogger.info('Testing timestamp field click and kubernetes_pod_id field search');
// Check if _timestamp field exists and click it
await pm.logsPage.clickTimestampField();
testLogger.info('Validated: "_timestamp" field found in field list and clicked');
// Click on schema button
await pm.logsPage.clickSchemaButton();
testLogger.info('Validated: schema panel opened');
// Search and click kubernetes_pod_id field
await pm.logsPage.fillIndexFieldSearchInput("kubernetes_pod_id");
testLogger.info('Validated: field search input filtered to "kubernetes_pod_id"');
await pm.logsPage.ensureFieldIsInteresting("kubernetes_pod_id");
testLogger.info('Validated: "kubernetes_pod_id" marked as an interesting field');
// Click on infoschema button
await pm.logsPage.clickInfoSchemaButton();
testLogger.info('Validated: info-schema panel opened');
// Click Clear button
await pm.logsPage.clickClearButton();
testLogger.info('Validated: clear button clicked — field selections cleared');
// Remove kubernetes_pod_id as interesting field
await pm.logsPage.clickInterestingFieldButton("kubernetes_pod_id");
testLogger.info('Validated: "kubernetes_pod_id" removed as an interesting field');
// Assert that _timestamp still exists
await pm.logsPage.expectTimestampFieldVisible();
testLogger.info('Validated: "_timestamp" field is still visible after clearing kubernetes_pod_id — field persistence confirmed');
testLogger.info('Timestamp field click and kubernetes_pod_id field search completed');
});
});