Files
openobserve/tests/ui-testing/pages/logsPages/logsQueryPage.js
T
Shrinath RaoandClaude Opus 4.7 e2813291ce test: stabilise alpha1 e2e tests via UI org-switch (#11528)
## Summary

Stabilises alpha1 cloud E2E tests across the Logs-Core shard. Fixes a
UI/API org mismatch root cause, adds env-aware query execution helpers,
and brings all 10 changed spec files into POM compliance.

## Root cause (original)

After Dex login, the Pinia store binds all API calls to the user's
*default* org. The URL `?org_identifier=...` alone does not update the
store. Ingestion went to the `.env` org while UI searches went to the
user's default org → empty results → cascading test failures.

## Changes

### Alpha1 environment / authentication
- **`global-setup-alpha1.js`** — switches active org via the navbar
dropdown after Dex login so the Pinia store adopts the correct org;
`context.storageState` captures the result for all test contexts
- **`playwright-alpha1.config.js`** — `ORGNAME` sourced purely from
`.env`; cloud-config override removed
- **`loginPage.login()` / `unflattened.spec.js`** — include
`?org_identifier=${ORGNAME}` in SPA navigation to prevent defaulting to
`_meta`
- **`unflattened.js openStreamDetail()`** — replaced broken
stream-name-text click with `Stream Detail` action button + Schema-tab
visibility wait

### Auto-run query handling (`auto_query_enabled` flag)
- **`logsPage.js`** — added `runQueryAfterModeChange()`: on alpha1
(`auto_query_enabled=true`) waits for the in-flight auto-query to
complete; on local triggers a fresh query. Fixes
`@interestingFieldsCRUD` and `@quickModeResults` tests that were passing
on alpha1 but failing locally because the old fix clicked Refresh
mid-auto-run (hitting Cancel)
- **`logsQueryPage.js`** — null-guard `textContent()` return in
`_toggleAutoRun()` (`|| ''`) to satisfy Playwright's `string | null`
type contract

### Merge conflict resolution
- **`logsPage.js enableQuickModeIfDisabled()`** — kept HEAD's version
that defines `quickModeIndicator` at the top of the function (used in
the retry loop); discarded main's version that opened the utilities menu
before the variable was defined

### POM compliance (Sentinel audit)
Added three new POM methods to **`logsPage.js`**:
- `watchForNotification(timeout)` — returns a promise for pre-action
race-condition-safe notification watching
- `expectNotificationContainsText(text)` — notification text assertion
- `waitForNotificationWithText(text, timeout)` — notification waiter
with text filter

Removed raw locators from spec files:
- **`logspage.spec.js`** — notification race pattern →
`watchForNotification()` + `expectNotificationContainsText()`; 5-line
raw histogram toggle block → `ensureHistogramState(true)` (POM method
already existed)
- **`logsqueries.spec.js`** —
`pm.logsPage.page.waitForSelector('.q-notification__message:has-text(...)')`
→ `waitForNotificationWithText()`
- **`jobSearch.spec.js`** — added `testLogger` import; replaced 14×
`console.log` with `testLogger.info`; raw Monaco editor locators (lines
158–168) → `logsPage.waitForQueryEditorVisible()` +
`logsPage.getQueryEditorText()` (both already in POM)

### Tags
- **`jobSearch.spec.js`** — all 8 tests now tagged: `@jobSearch,
@P0/@P1` with functional sub-tags (`@smoke`, `@crud`, `@validation`)
- **`logshistogram.spec.js`** — all 5 tests now include `@P1`

## Results

- All `logsquickmode.spec.js` tests pass on both local and alpha1
- Zero Sentinel critical issues remaining across all 10 changed spec
files
- Remaining warnings (old-style auth in `jobSearch.spec.js` /
`pagination.spec.js`, `pm.logsPage.page.reload()` in
`logsquickmode.spec.js`) are pre-existing tech debt deferred to a
separate PR

## Test plan

- [ ] Run `logsquickmode.spec.js` locally and on alpha1 — both should
pass
- [ ] Run full Logs-Core shard on alpha1: `npx playwright test --config
playwright-alpha1.config.js
playwright-tests/Logs/{join,logspage,logsquickmode,pagination,unflattened,logshistogram,jobSearch,logsqueries,logsAnalyzeDimensions}.spec.js`
- [ ] Verify global setup logs: `Switching active org to: <ORGNAME>` →
`Org switch complete`
- [ ] Confirm no `console.log` output in `jobSearch.spec.js` test run
(all replaced by `testLogger`)

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 16:19:54 +00:00

156 lines
5.5 KiB
JavaScript

import { expect } from '@playwright/test';
const testLogger = require('../../playwright-tests/utils/test-logger.js');
export class LogsQueryPage {
constructor(page) {
this.page = page;
this.queryEditor = '[data-test="logs-search-bar-query-editor"]';
this.dateTimeButton = '[data-test="date-time-btn"]';
this.relative15MinButton = '[data-test="date-time-relative-15-m-btn"]';
this.refreshButton = '[data-test="logs-search-bar-refresh-btn"]';
this.errorMessage = '[data-test="logs-search-error-message"]';
this.utilitiesMenuButton = '[data-test="logs-search-bar-utilities-menu-btn"]';
this.resetFiltersButton = '[data-test="logs-search-bar-reset-filters-btn"]';
this.resultDetail = '[data-test="logs-search-result-detail-undefined"]';
this.histogramToggle = '[data-test="logs-search-bar-show-histogram-toggle-btn"]';
this.sqlModeSwitch = { role: 'switch', name: 'SQL Mode' };
this.autoRunDropdownBtn = '[data-test="logs-search-bar-refresh-btn"] ~ button';
this.autoRunToggleItem = '[data-test="logs-search-bar-live-mode-toggle-btn"]';
this._autoQueryEnabledCache = undefined;
}
async setDateTimeFilter() {
await this.page.locator(this.dateTimeButton).click({ force: true });
await this.page.locator(this.relative15MinButton).click({ force: true });
}
async typeQuery(query) {
const queryEditor = this.page.locator(this.queryEditor);
await expect(queryEditor).toBeVisible();
await queryEditor.locator(".inputarea").fill(query);
}
async clickRefresh() {
await this.page.locator(this.refreshButton).click();
}
async clickErrorMessage() {
await expect(this.page.locator(this.errorMessage)).toBeVisible({ timeout: 30000 });
await this.page.locator(this.errorMessage).click();
}
async clickResetFilters() {
// Reset filters button is now directly on the toolbar
await this.page.locator(this.resetFiltersButton).click();
}
async clickNoDataFound() {
// Page-level search — the "No data" text may be in:
// SearchResult.vue histogram-empty span (warning icon + "No data found for histogram.")
// Index.vue h6[data-test="logs-search-error-message"] (info icon + "No events found…")
// Index.vue div[data-test="logs-search-result-not-found-text"] ("Result not found.")
// Use specific enough substrings to avoid matching unrelated empty-states.
const patterns = [
'No data found for',
'No events found',
'Result not found',
'No data found',
];
for (const pattern of patterns) {
const locator = this.page.getByText(pattern).first();
try {
await locator.waitFor({ state: 'visible', timeout: 10000 });
await locator.click({ force: true });
return;
} catch (e) {
continue;
}
}
throw new Error('No "no data" message matched — expected one of: ' + patterns.join(', '));
}
async clickResultDetail() {
await this.page.locator(this.resultDetail).click();
}
async waitForTimeout(milliseconds) {
await this.page.waitForTimeout(milliseconds);
}
async toggleHistogram() {
// Histogram toggle is now directly visible in the toolbar (moved out of utilities menu)
await this.page.locator(this.histogramToggle).click();
}
async isHistogramOn() {
// Histogram toggle is now directly visible in the toolbar (moved out of utilities menu)
const histogramToggle = this.page.locator(this.histogramToggle);
const isChecked = await histogramToggle.getAttribute('aria-checked');
return isChecked === 'true';
}
async ensureHistogramState(desiredState) {
const isOn = await this.isHistogramOn();
if (isOn !== desiredState) {
await this.toggleHistogram();
}
}
async isSQLModeOn() {
const sw = this.page.getByRole(this.sqlModeSwitch.role, { name: this.sqlModeSwitch.name });
return await sw.isChecked();
}
async ensureSQLMode() {
if (!(await this.isSQLModeOn())) {
await this.page.getByRole(this.sqlModeSwitch.role, { name: this.sqlModeSwitch.name }).click();
await this.page.waitForTimeout(500);
}
}
async ensureFTSMode() {
if (await this.isSQLModeOn()) {
await this.page.getByRole(this.sqlModeSwitch.role, { name: this.sqlModeSwitch.name }).click();
await this.page.waitForTimeout(500);
}
}
async _isAutoQueryEnabled() {
if (this._autoQueryEnabledCache !== undefined) return this._autoQueryEnabledCache;
this._autoQueryEnabledCache = await this.page.evaluate(async () => {
try {
const res = await fetch('/config', { credentials: 'include' });
if (!res.ok) return false;
const cfg = await res.json();
return cfg?.auto_query_enabled === true;
} catch {
return false;
}
});
return this._autoQueryEnabledCache;
}
async _toggleAutoRun(expectedLabel) {
if (!(await this._isAutoQueryEnabled())) {
testLogger.info(`auto_query_enabled=false on this env — skipping ${expectedLabel === 'Turn off' ? 'disableAutoRun' : 'enableAutoRun'}`);
return;
}
await this.page.locator(this.autoRunDropdownBtn).first().click();
const toggle = this.page.locator(this.autoRunToggleItem);
await expect(toggle).toBeVisible({ timeout: 5000 });
if (((await toggle.textContent()) || '').includes(expectedLabel)) {
await toggle.click();
} else {
await this.page.keyboard.press('Escape');
}
await this.page.waitForTimeout(300);
}
async disableAutoRun() {
await this._toggleAutoRun('Turn off');
}
async enableAutoRun() {
await this._toggleAutoRun('Turn on');
}
}