mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
## 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>
288 lines
13 KiB
JavaScript
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');
|
|
});
|
|
|
|
|
|
});
|