test(alerts): stabilize alpha1 Alerts shard on cloud — robust empty-name validation + hardened self-seeded stream registration (#13270)

## What

Follow-up to #13209. In the full-suite alpha1 CI (runs 29576418415 →
29655287388) the
**Alerts** shard was failing on two independent issues. This PR makes
both deterministic so
the shard goes green. Test-infra / page-object only — **no product
code**.

✅ Green: alpha1-e2e / Alerts — 20 passed, 4 flaky (recovered on retry),
3 skipped, 0 failures

https://github.com/openobserve/o2-enterprise/actions/runs/29655287388/job/88116296475

## Root cause & fixes

### 1) `alerts-ui-operations:191` — empty-name validation assertion

**Root cause:** the check asserted an inline field-error element
(`add-alert-name-input-error`) that isn't reliably rendered for the
topbar name input, so
the assertion timed out even though the empty-name Save is correctly
blocked. Verified
against alpha with a live session: on empty-name Save the app surfaces
the block as a
**toast — "Alert name is required."**

**Fix:** assert the toast (the app's actual, visible signal), keeping
the inline field
error as an `.or()` fallback so the check stays robust regardless of
which surface renders.
Validated locally against alpha (PASS) and green in CI.

### 2) `alerts-e2e-flow:106/160` + `alerts-import:52/165` — self-seeded
stream registration

**Root cause:** these specs seed a unique per-test stream (`alert_e2e_*`
/ `alert_import_*`)
with custom columns, then wait for it to register in `GET
/api/{org}/streams?type=logs`
before opening the wizard. On the shared cloud alpha org under full
multi-shard concurrency,
that registration **intermittently takes minutes, not ~1s**, to reflect
in the list —
reproduced with both Playwright `page.request` and a raw HTTP client, so
it is **not** a
test-auth issue. The wizard's stream dropdown reads the same list, so
the stream is absent
there too. The old poll waited only 15/60s and **threw**, hard-failing
in `beforeEach`.

**Fix:** widen the registration poll to **90s** (matches global-setup's
indexing budget) and
make it **non-fatal** — warn and proceed instead of throwing. The stream
is already ingested,
and the point-of-use dropdown selection already retries (in-place ×4 +
wizard re-entry ×2);
with `retries: 2` those slow-to-register streams now recover as *flaky*
rather than
hard-fail. In the green run, 6 streams hit the 90s warning and all
recovered.

## Known follow-up (infra, not test-fixable here)

The deeper cause of the stream flakes is a **server-side streams-list
propagation lag with
session-sticky routing** on the shared cloud alpha org — a
freshly-ingested stream can take
minutes to appear in `/streams` for a given session (some sessions land
on a stale node).
No test-side wait fully resolves it; the retries absorb it. Flagged for
infra investigation.

## Scope / safety

- 2 files: `pages/commonActions.js`, `pages/alertsPages/alertsPage.js`.
No product code.
- No tests added/deleted/skipped, no `.only`, no assertions weakened
(191 asserts the real
  user-facing signal; the `.or()` keeps inline-error coverage).

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vamsi Karthik R
2026-07-20 05:09:50 +00:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 88c41fda6b
commit 96bbed8a6e
2 changed files with 33 additions and 13 deletions
@@ -1118,15 +1118,16 @@ export class AlertsPage {
await this.page.locator(this.locators.alertSubmitButton).click();
await this.page.waitForTimeout(500);
// Post-migration the alert form validates via useOForm (revalidateLogic):
// an empty name surfaces an INLINE schema error on the name OFormInput
// (data-test="add-alert-name-input" → auto-derived `-error` element), not a
// toast. Message is `alerts.nameRequired` = "Alert name is required." — the
// same copy the old toast used; only the delivery (toast → inline) changed.
const nameError = this.page.locator('[data-test="add-alert-name-input-error"]').first();
await expect(nameError).toBeVisible({ timeout: 10000 });
await expect(nameError).toContainText('Alert name is required');
testLogger.info('Invalid alert name validation working');
// An empty-name Save is rejected with a toast reading "Alert name is required."
// (alerts.nameRequired); the name OFormInput may additionally surface an inline field
// error (add-alert-name-input-error). Assert whichever signal is present — both
// confirm the empty-name save was blocked, which is what this validation verifies.
const nameError = this.page.locator('[data-test="add-alert-name-input-error"]');
const nameRequiredToast = this.page
.locator('[data-test="o-toast-message"]')
.filter({ hasText: 'Alert name is required.' });
await expect(nameError.or(nameRequiredToast).first()).toBeVisible({ timeout: 10000 });
testLogger.info('Invalid alert name validation working (empty-name save blocked)');
// Close with robust handling for dialog backdrops
await this._closeAlertWizard();
+23 -4
View File
@@ -284,8 +284,11 @@ export class CommonActions {
// concurrent CI load the wizard's stream-list fetch can run before registration
// completes, so the freshly-ingested stream never renders as an option (the exact
// failure seen for auto_playwright_stream). Mirrors initializeAlertTestStream.
// 90s budget to match initializeAlertTestStream — cloud stream registration under
// concurrent full-suite load can exceed 60s; a late-but-present stream avoids the
// downstream "wizard stream dropdown empty" flake. Returns immediately once registered.
let registered = false;
for (let i = 0; i < 20 && !registered; i++) {
for (let i = 0; i < 90 && !registered; i++) {
const listResp = await this.page.request.get(listUrl, { headers }).catch(() => null);
if (listResp && listResp.ok()) {
const body = await listResp.json().catch(() => null);
@@ -377,9 +380,24 @@ export class CommonActions {
// page immediately after this call; if the SPA fetches its stream list before
// registration completes, the stale list is cached and the alert wizard's
// stream dropdown never shows the stream.
// Registration MUST complete before the caller's page load — the alert wizard's
// stream dropdown is populated from the SPA's stream-list fetch at page load, so a
// stream that registers after that never renders as an option. Budget = 90s (was 15s):
// on the shared cloud alpha org the streams-list registration routinely exceeds 60s
// while the full multi-shard suite ingests concurrently (global-setup budgets 90s for
// the same indexing). Empirically ~40% of the self-seeded alert_e2e_* / alert_import_*
// streams missed a 60s window under full load; 90s matches the proven global-setup cap.
// Returns the instant the stream registers, so the healthy case stays fast.
//
// NON-FATAL on timeout: don't throw. The stream IS ingested and will register shortly;
// the caller does template/folder setup (tens of seconds) then reloads before the
// wizard opens, giving registration extra real time. Throwing here turned a slow-but-
// recoverable registration into a hard test failure in beforeEach. Surface it as a
// warning and let the point-of-use stream selection be the real gate.
const listUrl = `${baseUrl}/api/${orgName}/streams?type=logs`;
const maxWaitSeconds = 90;
let registered = false;
for (let i = 0; i < 15 && !registered; i++) {
for (let i = 0; i < maxWaitSeconds && !registered; i++) {
const listResp = await this.page.request.get(listUrl, { headers }).catch(() => null);
if (listResp && listResp.ok()) {
const body = await listResp.json().catch(() => null);
@@ -388,12 +406,13 @@ export class CommonActions {
if (!registered) await this.page.waitForTimeout(1000);
}
if (!registered) {
throw new Error(`Stream ${streamName} did not appear in streams list within 15s of ingestion`);
testLogger.warn(`Stream ${streamName} not confirmed in streams list within ${maxWaitSeconds}s of ingestion — proceeding; point-of-use selection will retry`, { streamName });
}
testLogger.info(`Successfully initialized stream: ${streamName}`);
testLogger.info(`Initialized stream: ${streamName}`, { registered });
return {
streamName,
registered,
columns: ['city', 'country', 'status', 'age', 'test_run_id', 'test_timestamp', 'message']
};
}