Adds E2E regression cover for the fix in #13812.
The Logs → Build toggle mounts PanelFieldList with pageKey "build" in
edit
mode, which deferred its stream-list fetch until a stream was already
set.
Since ZO_QUERY_ON_STREAM_SELECTION defaults to true, the logs page
selects
nothing, so Build mounted blank and its Stream dropdown was left
enabled,
openable and permanently empty on "No options found" — no way to pick a
stream, no way out.
## Tests (Logs-SelectStar shard)
- **P0** — with no stream selected on the logs page, the Build tab's
Stream
dropdown is populated and a stream can actually be picked: no empty
state,
options render, `e2e_automate` is offered, selecting it lands, and the
field list loads that stream's schema.
- **P1** — changing Stream Type with a blank stream refetches the list:
logs → traces drops `e2e_automate` (a logs stream) and loads a real
traces
list, switching back restores it. Covers the stream_type watcher, which
is
gated on the same `initialStreamsLoaded` flag the fix arms.
`beforeEach` asserts the logs page starts with no stream selected —
without
that precondition both tests would pass against the buggy build.
## Notes
- Page-object locators are scoped to
`[data-test="logs-build-query-page"]`:
the Visualize tab mounts a second PanelEditor that stays in the DOM
under
`v-show`, so an unscoped `index-dropdown-stream` is a strict-mode
violation.
- Verified 6/6 green over 3 repeats locally. The shard runs the backend
with
ZO_QUICK_MODE_ENABLED=true, which the local instance does not —
assertions
avoid named fields so quick mode's field narrowing can't affect them.
## Why
The scheduled **Alpha1 (Cloud)** e2e run failed on three jobs
([o2-enterprise run
31547517666](https://github.com/openobserve/o2-enterprise/actions/runs/31547517666)):
**Logs-Features** (7), **Functions** (11), **RegressionSet** (4).
Root-caused each against a freshly-deployed alpha; two are genuine test
heals, one was pure environment skew.
## Root causes & fixes
### 1. Logs-Features — shareLink + monaco-query-prefill (7 tests) ✅
fixed
On cloud the app builds the share link from its **public** base URL
(`*.external.zinclabs.dev`), while the tests authenticate against the
ingress host (`*.internal.zinclabs.dev`). `page.goto(sharedUrl)`
navigated **cross-origin**, dropped the auth cookies, and bounced to Dex
— so every post-redirect state assertion read back `null`.
**Fix:** re-point the short URL's origin to `ZO_BASE_URL` in
`clickShareLinkAndGetUrl()` (right next to the existing HTTP→HTTPS
"maintain auth context" step). `/short/<id>` resolves identically on the
backend regardless of ingress host, so only the origin changes and the
session stays valid. Same-origin envs (localhost / matching deployed
host) are untouched.
### 2. RegressionSet — alerts Bug #9967 PromQL (1 test) ✅ fixed
`ingestMetricsData()` authenticated with `ZO_ROOT_USER_EMAIL:PASSWORD`,
but **cloud ingestion needs `email:passcode`**. The ingest silently
401'd → the `e2e_test_cpu_usage` metrics stream was never created → the
PromQL alert flow timed out selecting it.
**Fix:** build auth via `getAuthHeaders()` (passcode on cloud, root
creds on self-hosted), refresh the passcode from the live browser
session first, and poll until the stream is indexed so the wizard's
stream-select doesn't race WAL lag.
### 3. Functions — radio → segmented toggle (11 tests) — no change
needed
The page object was already migrated to the segmented toggle in #13766;
these failures were the **alpha frontend lagging the merged test code**.
Green after the redeploy with zero code change.
## Verification (against alpha, `auto_org_1`)
- `shareLink.spec.js` + `monaco-query-prefill.spec.js` → **11/11
passed**
- `alerts-regression.spec.js` (full) → **5/5 passed** (#9967, #10110,
#10899, #10872, #10472)
- `Functions/js-transform-type.spec.js` → **7/7**,
`row-expansion.spec.js` → green
## Scope
OSS-only (test files). The ENT `playwright_alpha1.yml` checks out
opensource at `main`, so merging makes the next scheduled run pick this
up. It can also be pre-verified by dispatching that workflow with
`o2_opensource_repo=test/alpha1-e2e-heals-0812`.
Supersedes #13738, whose commit is included here as the first of four.
Adds PromQL support to count and time-sliced SLOs, lets metrics streams
choose SQL or PromQL, and fixes three shipped bugs found along the way.
## Shipped bug: every SQL time-slice SLO recorded wrong history
`fetch_rows`' `SliQueryPlan::Single` arm normalized every hit with
`with_good_column: true`, but that arm serves **two** shapes and only
one projects `zo_slo_good`:
| shape | projects | result |
|---|---|---|
| count single-query | `zo_slo_good` + `zo_slo_value` | correct |
| time slice | `zo_slo_value` only | `good` defaulted to `0.0` |
So every SQL time-slice slice was classified from `0.0` instead of its
aggregate. With a positive threshold, `<`/`<=` (e.g. `p95 < 300ms`)
recorded every slice **good** — the SLO read ~100%, the budget never
burned, and no alert on it could fire. `>`/`>=` recorded every slice
**bad** and fired continuously. Shipped in v0.92.0 (`4772df1723`);
`to_row` had no time-slice test, which is how it got through.
Fixed by deriving the flag from the SLI type in a new
`single_query_rows`. Deliberately **not** fixed inside `to_row` by
falling back to `total` — that would read a count bucket whose `SUM`
came back NULL as *fully good*, inventing uptime. A test forbids it.
**Stored history is not rewritten.** It ages out within one window; an
operator can force a rebuild sooner by making a real edit (a no-op
re-save does nothing — `update` bumps only on `definition_changed`). No
auto-migration: bumping every time-slice SLO at boot would freeze every
attached alert org-wide until backfill cleared the coverage floor, and
double each org's row budget for 97 days — a self-inflicted monitoring
gap to repair a reporting error.
## PromQL support
**Time slice** carried a `query_language` field the planner ignored, so
a PromQL time-slice SLO saved cleanly and measured nothing, forever.
- `SliQueryPlan::PromQlValue` — one expression, not a good/total pair,
planned at slice ends from the same helper the count arm uses.
- The expression is passed through **verbatim**. Grouping comes from
series labels; wrapping it in `sum by (...)` the way the SQL arm injects
GROUP BY would destroy the aggregate (the sum of four pods' p95 is not a
p95).
- `promql_value_rows` **rejects** two series colliding on one
`(slice_start, group_key)` via `RejectReason::AmbiguousSeries` instead
of summing. Summing is right for counters (what `promql_rows` does) and
meaningless here. Detection is unconditional, so the ungrouped case — an
unaggregated `histogram_quantile` returning one series per instance — is
covered. Rejects are plumbed through `fetch_rows` into
`PassResult.rejected` on both the live and backfill paths.
**Count** already had a working `CountSource::PromQl` backend that no UI
could reach.
## Metrics streams choose SQL or PromQL
`language_suits_stream` refused SQL whenever `stream_type == "metrics"`,
and the form derived the language from the stream type — so metrics had
no SQL option. A metrics stream is an ordinary stream with a `value`
column, and `SELECT histogram(_timestamp, '5 minute'), avg(value) FROM
cpu_usage` is a perfectly good SLI. SQL is now valid for any stream
type; PromQL stays metrics-only. The form asks via a toggle (defaulting
to PromQL) instead of deriving.
## Validation
- `scope` refused for PromQL — a SQL `WHERE` fragment with nowhere to go
in a PromQL plan.
- Empty `query` refused for **both** languages (the SQL aggregate was
previously never validated — same silent-freeze shape).
- Expressions parsed with `promql-parser` at save, in `core` (`config`
has no parser).
- **Parse-only by design.** A grouping check requiring the root to be an
aggregation whose `by(...)` matches `group_by` wrongly rejects
`histogram_quantile(0.95, sum by (le, region)(...))` (function-call
root; `le` is a necessary label superset) and binary-op roots like `sum
by (region)(increase(a[5m])) - sum by (region)(increase(b[5m]))`. A
"warn instead" variant needs a warnings channel the save API doesn't
have.
## Also fixed
- **Time-slice comparator 422.** `SliConfig::TimeSlice.comparator` has
no serde default, so a dropdown the user never opened was omitted and
the save died with a bare 422 — blocking creation of *any* time-slice
SLO from the UI. Seeded to `<` only when blank, so a stored comparator
survives an edit.
- **SLO/alert/service-stream regressions** (the commit from #13738):
fractional-threshold serde round-trip, `SloAlertCondition` UI, alerts
table, service streams.
## Verification
- 487 config · 206 core · 45 infra · 418 frontend — all passing; clippy
`-D warnings`, `cargo fmt`, prettier, eslint, `vue-tsc` all clean.
- Driven through a real browser against a running server (isolated
instance, own data dir). All four SLI/language combinations create and
store correctly: count·PromQL, count·**SQL over metrics**,
time-slice·PromQL, time-slice·**SQL over metrics**. Also verified: the
toggle swaps field shapes both ways, scope shows for SQL and hides for
PromQL, PromQL-only hints appear only in PromQL mode, and editing a
stored SQL-over-metrics SLO reopens as SQL with its predicate intact.
- Validation rejections confirmed live with actionable messages:
PromQL-with-scope, unparseable PromQL, and an empty SQL aggregate.
Known limitation, pre-existing: `threshold` likewise has no serde
default and is not seeded, so leaving it blank still 422s. That is
genuine user error rather than an untouched-control trap; adding form
validation was out of scope.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Summary
Completes the Sentinel **Page Object Model** compliance sweep of the
Playwright suite (follow-up to #13727, which covered the non-Dashboards
shards). Every raw `page.locator(...)` / `page.getBy*(...)` selector —
in **actions and assertions** — is relocated out of the spec files into
the relevant page object; specs now call page-object methods/getters
only.
**Result: 0 raw fixture-`page` selectors across the entire
`playwright-tests` suite** (single-line *and* multi-line, verified with
a dotall/multiline scanner). Together with #13727, the whole suite is
POM-clean.
## What's in this PR
**Dashboards shards — ~1,460 selectors** relocated into their page
objects (`dashboard-panel-configs/actions/edit/time`, `dashboard-chart`,
`dashboard-variables-scoped`, `dashboard-filter`, `dashboard-drilldown`,
`crossLinkPage`, `visualise`, `dashboard-share-export`,
`dashboard-create`, `dashboard-settings`, `dashboard-variables`):
| Shard | selectors |
|-------|----------:|
| Dashboard-Config-Settings (11 specs) | ~255 |
| Dashboards-Variables (12 specs, 4 batches) | ~693 |
| Dashboards-Tables (7 specs) | ~127 |
| Dashboards-Charts (9 specs) | ~81 |
| Dashboards-Core + Visualize (10 specs) | ~144 |
| Dashboards-Settings + Panel-DateTime (8 specs) | ~107 |
| Dashboard-Regression (1 spec) | ~52 |
**Correlation UI specs** (3 new `Correlation/*.ui.spec.js` that arrived
via main's merge, after the original audit) — converted into a new
`correlationDrawerPage` + an extended `correlationSettingsPage`, with
`PageManager` wired into specs that previously used none.
**Audit stragglers** the original **single-line** audit grep
undercounted — multi-line `page\n.locator(...)` chains in
`logstable.spec.js` (→ `logsPage.getTableHeaderByField()`) and
`dashboard-streaming.spec.js` (2 add-panel selectors).
**AI-review fixes** (DeepSeek, on this PR) — removed 2 dead-code
duplicate getters + 1 redundant method alias (merge artifacts from the
sequential batch commits).
One Node-context `console.log` (`dashboard-general-setting`'s
`beforeEach`) → `testLogger.info`.
## Approach
Nearly every Dashboards spec shares the same page objects, so the work
was done in **sequential batches** (one committed, verified unit at a
time) to keep page-object edits conflict-free. 13 commits, mapping 1:1
to those batches.
## Not changed (by design)
- **`waitForTimeout`** left as-is — logged in the audit, not rewritten
(per direction).
- **Browser-context code inside `page.evaluate()` /
`waitForFunction()`** (`document.querySelector`, `window.*`) left as-is
— it runs in the page's JS engine, not Node, so `testLogger` (a Node
module) can't be called there. *No `console.*` remained in dashboards
specs*; the Node-context ones were converted to `testLogger`.
- **`newPage.locator(...)`** on separate popup `Page` instances left
as-is (not the fixture `page`).
## Verification
- **0 raw fixture-`page` selectors** across all specs — single- and
multi-line, comments stripped.
- All changed `.js` files pass `node --check`.
- **CI-gated** for browser execution (no local runs), consistent with
#13727. The enterprise E2E crosscheck already passed against this PR.
## Stats
82 files changed (+3,355 / −2,211) — 61 specs, 21 page objects.
Selectors **moved into page objects, not deleted**.
## What & why
Sentinel (Council of Agents) audit-driven **Page Object Model
compliance** across the Playwright suite. Many specs (a lot added
recently, some AI-generated) had raw `page.locator(...)` /
`page.getBy*(...)` selectors directly in spec files — including inside
`expect(...)` — which the Sentinel flags as CRITICAL. This PR relocates
those selectors into their page objects and calls page-object
methods/getters instead.
A full audit of **all 256 specs** was generated first (stored locally
under `tests/ui-testing/MD_Files/sentinel-audit-2026-08-10/`, which is
gitignored). Original totals: **1,609** raw selectors, **190** inside
assertions, plus console/testLogger items.
## Scope of THIS PR (non-Dashboards)
Every non-Dashboards spec is now **100% raw-selector clean** (verified:
0 occurrences). Shards covered:
- **GeneralTests, Logs (Core/Builder-Advanced/Features/Regression),
Traces (+Reg), Streams (+Reg), Metrics (+Reg), Pipelines, Reports
(+Reg), Anomaly, Login/auth, auth-warm, and the unmatched Alerts
specs.**
- `console.*` → `testLogger` where it was Node-context (kept `console.*`
**inside `page.evaluate()`** — that's browser context; `testLogger`
doesn't exist there).
- `testLogger` import/use added where missing.
- **`waitForTimeout` intentionally left as-is** (logged in the audit,
not rewritten — per direction).
## Deferred to a follow-up PR
The **heavy Dashboards shards** (Variables 693, Config-Settings 255,
Tables 127, Charts 81, Core 79, Visualize 65, Settings 52,
Panel-DateTime 55, Dashboard-Regression 52 raw selectors) are **not** in
this PR — they're the bulk of the work and will be a separate, focused
pass. (dashboard-streaming console cleanup is included here.)
## Verification
Fixes were applied and executed **shard by shard** against a local env,
gated green before commit where the env allowed:
| Group | Local result |
|-------|--------------|
| GeneralTests (edited specs) | 52 passed / 11 skipped / 0 failed |
| Logs (shareLink, logsQuickPick, indexquery) | 21 passed |
| Traces / Streams / Metrics | 49 passed / 2 skipped / 0 failed |
| Pipelines | 56 passed / 17 skipped / 0 failed |
| logs-bugs :548, dashboard-streaming | verified not regressed
(isolation) |
**Alerts create-wizard specs** (Alerts shard, Alerts-Regression,
unmatched Alerts) could not be gated locally — the alert-creation
wizard's stream popover times out on this env **on unmodified code too**
(reproduced on original, test `:363`), so those refactors are 1:1
selector→method mappings, syntax-clean (`node --check`), and rely on CI
to gate.
Every changed `.js` passes `node --check`. `waitForTimeout` unchanged;
no behavior changes intended.
## Stats
51 files changed. Selectors moved into page objects, not deleted.
🤖 Auto-generated E2E tests for #13652 — **healed to passing** against a
live OpenObserve before this PR was opened.
- Branch `autoe2e/pr-13652` (stacked on `main`), so the tests run
against the feature code and pass before it merges.
- Spec:
`tests/ui-testing/playwright-tests/Logs/streamSchemaTimezone.spec.js`
The repo Playwright CI re-validates here. Review is for **quality +
coverage** — the tests already pass.
_Generated by the E2E Council pipeline._
---------
Co-authored-by: Shrinath-O2 <214651540+Shrinath-O2@users.noreply.github.com>
## Problem
On the **alpha1 (cloud)** e2e run, the **Logs-Core** shard failed and
did not recover across all 3 retries. The single hard failure was:
`Logs/unflattened.spec.js:220 › Unflattened testcases › stream to toggle
store original data toggle and display o2 id`
### Root cause
After switching to the **unflattened** tab, the test closed the
log-detail drawer with a bare `closeDialog.waitFor()` +
`closeDialog.click()`. Switching that tab re-renders the drawer, so the
close-button node **detaches mid-click** — Playwright logs `element was
detached from the DOM, retrying` and the click hangs for the full 45s,
failing every retry identically. It's a deterministic race, which is why
retries never helped. The secondary retry-failure signature (JSON detail
panel lagging behind tab activation) is the same drawer instability,
wider on the slower shared alpha env.
## Fix
- Close the drawer via the file's existing resilient
`closeLogDetailDrawerIfOpen()` helper (Escape fallback +
wait-for-hidden) in **both** the failing test and its identical
quick-mode sibling (`unflattened.spec.js` lines ~283 and ~426).
- Harden `openJsonDetailTab()` (`pages/logsPages/unflattened.js`) to
re-assert the JSON tab and re-wait once when the panel content lags,
then fall back to the original throwing wait so a genuine non-render
still fails loudly.
Test-only change; no product code touched.
## Verification
Seeded the **Run E2E Tests on Alpha1 (Cloud)** workflow against this
branch (`o2_opensource_repo=test/alpha1-unflatten-drawer-close-flake`,
enterprise on `main`): [run
31067277452](https://github.com/openobserve/o2-enterprise/actions/runs/31067277452)
— Logs-Core and the overall run passed green.
## 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".
## Alpha1 (Cloud) E2E — shard-by-shard failure fixes
Fixes real, root-caused failures from Alpha1 run
[30876689608](https://github.com/openobserve/o2-enterprise/actions/runs/30876689608)
(5 red shards, 16 hard failures). Each root cause was reproduced live
against alpha via Playwright MCP. **8 of 16 hard failures are fixed here
in code**; the remaining 8 are environment/data-timing driven (backend
instability during the run's 04:15–04:18 ingest window) and need a
re-run, not a code change.
### Fixes
| Shard | Fix | Root cause |
|---|---|---|
| **Alerts** (4) | `alerts-advanced.spec.js` destination URL →
`https://example.com/webhook` | The self-host URL
`${ZO_BASE_URL}/…/alerts_notify_sink/_json` resolves to a **private IP**
on cloud, so the backend **SSRF guard rejects the save** → the New
Destination form never closes → `createDestination()` times out, failing
all 4 Alerts-Advanced tests. Verified live: private-IP URL is blocked,
`example.com` (public, IANA-reserved, never delivered to) saves and
closes the form. |
| **GeneralTests** (2 + 2 flaky) | sanity `beforeEach` →
`setDateTimeToPast1Hour()` (+ new `logsPage` helper) | The
result-summary/pagination tests query the default **Past 15 Minutes**.
`e2e_automate` ingestion is bursty on shared/cloud envs — **0 rows in
15m vs 7,850 in 1h** (confirmed via API) — so the result table never
renders. This is exactly the failed-vs-flaky split seen on the run. |
| **Logs-Features** (1) | `monaco-query-prefill.spec.js` → poll
`getQueryEditorTextWhenReady()` | Lazy Monaco prefills its model
asynchronously after a saved view is applied on a fresh page load. A
one-shot read after a fixed 2s caught an empty editor (`Received: ""`).
Now polls until the query lands. Non-masking: if it never prefills, the
poll still times out and the test fails. |
| **Traces** (1 of 2) | `tracesSearch.spec.js` → poll for a settled
results/no-results state | The "no stream selected state" test read
`hasTraceResults()`/`isNoResultsVisible()` once each; the async search
settle left both momentarily false. Now polls until one terminal state
renders. |
### Not changed (deliberately)
- **Traces `traceQueryEditor.spec.js:151`** — the detail-tree check
already waits 10s; its failure means the error-trace detail didn't open,
which needs error-trace data in-window to validate. Weakening the
assertion would mask real behavior.
- **GeneralTests `enrichment.spec.js:101`, `logoManagement.spec.js:18`**
— ran during the backend-instability window and can't be reproduced
live. `[name='home']` is **not** stale (present live) and `_meta` access
is fine for the test user. Env/timing.
- **Pipelines (all 5)** — UI-click timeouts (`…-update-pipeline` /
`Explore` button not found) because pipeline creation / stream data was
unavailable while the backend returned *"No online node" / "Proxy
request" / "Expected N records, got 0"*. Same selectors 42 passing tests
in the shard use. Env-driven → re-run to confirm.
### Validation
- All fixes reproduced/verified live on alpha via Playwright MCP
(`e2etest1@o2-qa.com`, `auto_org_1`).
- All 4 edited specs parse and list (`playwright test --list`).
- Full local runs are blocked by an unrelated issue: the Dex login UI
changed (new "Continue with Email" flow) so `global-setup.js` can't
authenticate locally; CI uses the shared-auth artifact. (Worth a
separate fix.)
## Problem
In [o2-enterprise run
30791574230](https://github.com/openobserve/o2-enterprise/actions/runs/30791574230)
the **Logs-Features** shard logged **2 failed, 6 flaky, 3 skipped, 63
passed** with **52 retries**. Seven of the eight troubled tests are in
`logs-autocomplete-suggestions.spec.js` (the eighth `code`-field test
shares the cause), and every one dies on the identical assertion:
```
Error: Expected to find a field with captured values
expect(received).not.toBeNull()
Received: null
```
— i.e. the shared `findFieldWithStringValues()` helper returned `null`.
## Root cause
A **click-time race**, not missing data (the `e2e_automate` stream holds
3848 rows ingested at shard start). The helper clicked a field's sidebar
expand button and then polled IndexedDB for 10s — but the 10s window
started at **click time** and raced the `/_values_stream` fetch. In the
failing run the runner was slow (a perf assertion showed queries ~2.6×
over baseline: 1055ms vs <403ms). The values response landed **after**
the IndexedDB poll had already given up, so the record read as `null`
and the assertion failed hard. Because `findFieldWithStringValues()` is
shared across ~8 tests, one slow window knocked out the whole cluster,
and each burned up to 4 attempts → 52 retries.
An intended fix (`clickFieldExpandAndWaitValues`, a deterministic
`/_values_stream` response wait) already existed in the file but was
**never wired in**.
## Fix
- **`expandFieldAndCaptureValues()`** — anchor the IndexedDB
confirmation **after** the actual `/_values_stream` response instead of
racing it from click time; re-click to re-expand if a prior collapse
meant no fetch fired. Replaces the dead `clickFieldExpandAndWaitValues`
helper and the bare click+poll in the `code`-field test.
- **`findFieldWithStringValues()`** — scan via the anchored capture,
with a bounded refresh-and-retry round so a transient empty `/_values`
response self-heals rather than failing the test.
- **Readiness gate** — add `waitForStreamData()` to every `beforeEach`
so freshly-ingested data is searchable before the UI drives value
capture (WAL → index lag). Best-effort so an already-warm shared stream
never blocks.
## Testing
- `node --check` passes; no linter is configured for the suite.
- The slow-runner condition can't be reproduced deterministically
locally; the change is a test-harness reliability fix (deterministic
response-anchoring + readiness gate) that removes the timing dependency
that caused the flake. Will confirm green via the ENT playwright
regression this PR dispatches.
🤖 Auto-generated E2E tests for #13025 — **healed to passing** against a
live OpenObserve before this PR was opened.
- Branch `autoe2e/pr-13025` (stacked on `issue-ent-2038`), so the tests
run against the feature code and pass before it merges.
- Spec: `tests/ui-testing/playwright-tests/Logs/logsQuickPick.spec.js`
The repo Playwright CI re-validates here. Review is for **quality +
coverage** — the tests already pass.
_Generated by the E2E Council pipeline._
**Test cases added / changed:**
| Test case | Type | What it verifies |
|---|---|---|
| `should quick pick a stream from the sidebar` | new | clicking a
sidebar quick pick button selects the stream and loads the field list |
| `should show hero state on fresh arrival with no stream selected` |
new | the main content area shows both hero action cards when no stream
is selected |
| `should open stream dropdown when clicking select stream card` | new |
clicking the "Select a stream" hero card opens the stream dropdown
popover |
| `should reappear quick pick after deselecting and re-select works` |
new | after deselecting a stream the quick pick reappears and
re-selection works correctly |
| `should show more footer when org has more than 8 streams` | new | the
quick pick more footer is visible with the correct remaining count when
org has > 8 streams (skips otherwise) |
| `should select stream via hero recent chip` | new | clicking a
recent-stream chip in the hero state selects the stream and triggers
field list loading |
| `should open query guide documentation in new tab` | new | clicking
the "Read the query guide" card opens the OpenObserve docs in a new
browser tab |
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>