Files
Vamsi Karthik RandClaude Opus 4.8 fddf2cf264 test(e2e): stabilize alpha1 SDR, GeneralTests & Metrics shards (stream/pattern-readiness + langTranslation OOM + Reka-table & editor races) (#13259)
## What

Stabilizes the **SDR**, **GeneralTests** and **Metrics** shards of the
alpha1 cloud
E2E suite (`playwright_alpha1.yml`) to green — the last three shards
remaining after
the rest of the suite was greened. Rebased on `main`.

The original failing run had, across these shards: **SDR** 1 hard fail +
1 flake,
**GeneralTests** 21 fails + 2 flakes, **Metrics** a load-sensitive
query-persistence
flake. Root causes were cloud data/UI-readiness races (freshly-created
streams &
regex patterns lagging their list/table; browser OOM under parallelism;
revamped Reka
UI re-rendering mid-interaction), all fixed with deterministic waits on
real signals.

**No `waitForTimeout` as a fix, no `force:true`, no `.first()`/`.nth()`
band-aids, no
retry-count bumps to hide failures, no weakened assertions. No
worker-count,
parallelism, playwright-config or CI/yml changes** — test code only.

## Root cause & fixes

**SDR**

1. **`scrollIntoViewIfNeeded` 45s timeout** (`multipleSDRPatterns`,
`queryTimeDrop`).
The revamped Reka streams/schema table re-renders rows mid-animation, so
the
scroll's stability polling spun to the 45s action timeout even though
the element
was visible. Guard with `expect(el).toBeVisible()` + an instant native
`scrollIntoView`; removed the trailing sleeps.
(`streamAssociationPage.js`)

2. **Stream Detail `click()` 45s timeout.** One-shot JS-then-Playwright
click raced the
re-rendering row. Replaced with a `toPass` retry that re-resolves the
button fresh,
   fires a native DOM click, and verifies the detail sidebar opened.

3. **Regex-pattern create→list eventual consistency** (6 combined tests:
`ingestionTime*`,
`queryTime*`). Create is accepted (`verifyPatternCreatedSuccess` passes)
before it
   propagates to the list read-replica, so the UI text-count check threw
"not found in list". Gate on the backend source of truth:
`waitForPatternCreated`
   polls `GET /re_patterns` until present then confirms the UI rendered;
`waitForPatternDeleted` fixes the symmetric "Unable to delete … Reason:
unknown"
   delete race. (`sdrPatternsPage.js`)

4. **Freshly-ingested stream lags the `/streams` table**
(`multipleSDRPatterns` @slow).
`openStreamDetail` reloads + re-searches until the stream's row appears
(bounded),
instead of a one-shot 5s wait. (Local-caught gotcha:
`isVisible({timeout})` does
   **not** auto-wait — switched to `waitFor({state:'visible'})`.)

5. Transient cloud-gateway **connection timeouts** on `page.request`
pattern calls wrapped
   in a connection-error-only retry.

**GeneralTests**

6. **cipherKeys ×6 — tab never visible.** Cipher Keys shares the exact
`visible: isEnt`
gate as Regex Patterns (`settings/index.vue`), and SDR's regex
management passes on
this same cloud env — so the tab **does** render; the test was clicking
the Settings
menu, which lands on the SectionHub where per-tab data-tests aren't
rendered. Fixed
by a **direct `goto('/web/settings/cipher_keys')`**, mirroring the
working regex page.
   (Not skipped — that would have masked a real, fixable nav bug.)

7. **enrichment ×7 — `pipeline-section-tab-enrichmentTables` 45s
force-click.** The tab
only mounts after the pipelines page header renders; the
flyout→pipelines→section-tab
nav is fragile under load. Replaced with direct SPA nav to the
enrichment-tables route
+ `goto` fallback + a `toPass` settle-retry Add-button click.
(`pipelinesPage.js`)

8. **languageTranslation ×8 + landingPage ×1 — browser OOM cascade**
("Target page/context/
browser has been closed"). 10 exhaustive ~27-page full-app crawls ran
`mode:parallel`
under `--workers=5`; a worker's browser was OOM-killed and every test
after it
cascaded. **Kept parallel** (serializing overran the shard timeout) —
the crawl now
navigates `waitUntil:'domcontentloaded'` + a bounded settle instead of
waiting up to
10–30s for `networkidle` on chart/websocket-heavy pages, cutting each
heavy page's
peak-memory dwell ~10s→~3s (so 5 concurrent crawls no longer spike
together) and making
each crawl ~3–4× faster. Added fail-fast on a closed page/context so any
residual crash
   surfaces at its true origin instead of being swallowed/retried.

9. **schema ×2 — `selectStream` can't find just-created
`test1`/`test3`.** Local reproduction
proved the root cause: the **fixed** stream names hit `400 "stream is
being deleted"`
(a prior run's cleanup still finishing) → the stream got no data → never
listed. Fixed
with **unique per-invocation stream names**; also hardened the ingestion
helper
(status-check + transient retry) and added a streams-API readiness gate
before
   `selectStream`. (`schema.spec.js`)

10. **enrichment `@upload` ×2 — `_search` `waitForResponse` timeout.**
The 30s search wait
was armed **before** the up-to-120s `selectStream`, so it expired
mid-selection. Re-armed
it (200-gated) immediately before `applyQuery`, plus a streams-API
readiness gate for
`e2e_automate`. `schemaload` gated on the `streams?type=logs` response
before reload.

**Metrics**

11. Replaced `waitForTimeout`s in `metricsPage.js` with real signals
(`data-selected="true"`,
`data-state="on"`) and fixed a reka `OToggleGroupItem` state-detection
bug (active state
    is `data-state="on"`, not a `selected` class).

12. **`metrics-promql-query-persistence` load-sensitive flake.**
`selectFirstStream` returned
before the async auto-populate finished, and persistence tests read the
query editor
before Monaco re-rendered after tab switches. Added editor-readiness
gates on real
    signals (auto-populated query present; Monaco mounted + laid out) in
    `metricsQueryEditorPage.js`; assertions unchanged.

## Validation

- **CI — SDR / GeneralTests / Metrics green (alpha cloud):**
  https://github.com/openobserve/o2-enterprise/actions/runs/29633444186
- **Original failing run:**
https://github.com/openobserve/o2-enterprise/actions/runs/29481437880
- **Local (alpha cloud, CI conditions, single-worker):** each
previously-failing test re-run
  to green — `schema.spec.js` 3/3, `multipleSDRPatterns.spec.js` 2/2.

## Notes

- 21 test files change (8 page objects + 13 specs). No shared
cleanup/setup, config, or
  workflow files touched.
- RegressionSet was unaffected by this branch — a transient hang in the
workflow's
"pre-test cleanup" step (before any test runs), unrelated to these
changes.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 05:45:02 +00:00

165 lines
6.7 KiB
JavaScript

const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
const testLogger = require('../utils/test-logger.js');
const PageManager = require('../../pages/page-manager.js');
test.describe("Query Time Drop - Combined Test", { tag: '@enterprise' }, () => {
test.describe.configure({ mode: 'serial' });
let pm;
// Generate unique test run ID for isolation
const testRunId = Date.now().toString(36);
// All patterns used in this spec file - with full pattern definitions for uniqueness
const patternsToTest = [
{
name: `url_format_query_drop_${testRunId}`,
description: 'URL validation pattern (for query time drop tests)',
pattern: '^https?://[\\w.-]+\\.[\\w.-]+.*$',
field: 'website_url',
value: 'https://openobserve.ai/docs'
},
{
name: `strong_password_query_drop_${testRunId}`,
description: 'Strong password pattern (for query time drop tests)',
pattern: '^[A-Za-z0-9@$!%*?&]{8,}$',
field: 'user_password',
value: 'Xyz@1234'
},
{
name: `ipv4_address_query_drop_${testRunId}`,
description: 'IPv4 address format (for query time drop tests)',
pattern: '^(\\d{1,3}\\.){3}\\d{1,3}$',
field: 'server_ip',
value: '192.168.1.100'
},
{
name: `hex_color_query_drop_${testRunId}`,
description: 'Hexadecimal color code (for query time drop tests)',
pattern: '^#[0-9a-fA-F]{6}$',
field: 'theme_color',
value: '#1A2b3C'
}
];
// Use unique stream name to avoid "stream being deleted" conflicts
const testStreamName = `sdr_query_drop_${testRunId}`;
test.beforeEach(async ({ page }, testInfo) => {
testLogger.testStart(testInfo.title, testInfo.file);
await navigateToBase(page);
pm = new PageManager(page);
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
testLogger.info('Combined query time drop test setup completed');
});
test("setup: cleanup patterns", {
tag: ['@sdr', '@cleanup', '@queryDrop']
}, async ({ page }) => {
testLogger.info('=== SPEC-LEVEL CLEANUP: Cleaning up patterns for combined query drop test ===');
for (const pattern of patternsToTest) {
testLogger.info(`Checking and cleaning up pattern: ${pattern.name}`);
await pm.sdrPatternsPage.navigateToRegexPatterns();
const exists = await pm.sdrPatternsPage.checkPatternExists(pattern.name);
if (exists) {
testLogger.info(`Pattern ${pattern.name} exists. Deleting (and unlinking if needed).`);
const deleteResult = await pm.sdrPatternsPage.deletePatternByName(pattern.name);
if (!deleteResult.success && deleteResult.reason === 'in_use') {
testLogger.info('Pattern in use, unlinking from streams first');
for (const association of deleteResult.associations) {
await pm.streamAssociationPage.unlinkPatternFromField(
association.streamName,
association.fieldName,
pattern.name
);
}
// Delete again after unlinking
await pm.sdrPatternsPage.navigateToRegexPatterns();
await pm.sdrPatternsPage.deletePatternByName(pattern.name);
}
testLogger.info(`✓ Pattern ${pattern.name} deleted successfully`);
} else {
testLogger.info(`✓ Pattern ${pattern.name} does not exist, no cleanup needed`);
}
}
testLogger.info('=== SPEC-LEVEL CLEANUP COMPLETE ===');
});
test("should drop multiple fields at query time when patterns are applied", {
tag: ['@sdr', '@queryDrop', '@all', '@combined']
}, async ({ page }, testInfo) => {
testInfo.annotations.push({ type: 'dependency', description: 'setup: cleanup patterns' });
testLogger.info('=== Testing combined drop for 4 patterns at query time ===');
// STEP 1: Ingest data once (data will remain unchanged in storage)
testLogger.info('STEP 1: Ingest 4 lines of data (before any patterns are applied)');
const dataToIngest = patternsToTest.map(p => ({
fieldName: p.field,
fieldValue: p.value
}));
// Query-time SDR transforms data at search time, so STEP 1 (visible) and STEP 4
// (dropped) inspect the SAME ingested batch — reuse one marker for both.
const ingestMarker = await pm.logsPage.ingestMultipleFields(testStreamName, dataToIngest);
// Verify all fields are visible before drop
const fieldsBeforeDrop = patternsToTest.map(p => ({
fieldName: p.field,
shouldBeDropped: false
}));
await pm.sdrVerificationPage.verifyMultipleFields(pm.logsPage, testStreamName, fieldsBeforeDrop, ingestMarker);
testLogger.info('✓ STEP 1 PASSED: All fields visible without SDR');
// STEP 2: Create all 4 SDR patterns
testLogger.info('STEP 2: Create all 4 SDR patterns');
for (const patternConfig of patternsToTest) {
await pm.sdrPatternsPage.navigateToRegexPatterns();
await pm.sdrPatternsPage.createPattern(patternConfig.name, patternConfig.description, patternConfig.pattern);
await pm.sdrPatternsPage.verifyPatternCreatedSuccess();
// Verify pattern was created
await pm.sdrPatternsPage.navigateToRegexPatterns();
const exists = await pm.sdrPatternsPage.waitForPatternCreated(patternConfig.name); // backend-consistent create gate (API source of truth)
expect(exists).toBeTruthy();
testLogger.info(`✓ Created and verified pattern: ${patternConfig.name}`);
}
testLogger.info('✓ STEP 2 PASSED: All 4 patterns created and verified');
// STEP 3: Link all 4 patterns to their respective fields with QUERY TIME drop
testLogger.info('STEP 3: Link all 4 patterns to their respective fields with DROP action');
for (const patternConfig of patternsToTest) {
await pm.streamAssociationPage.associatePatternWithStream(
testStreamName,
patternConfig.name,
'drop',
'query', // QUERY TIME - key difference from ingestion time
patternConfig.field
);
testLogger.info(`✓ Linked pattern ${patternConfig.name} to field ${patternConfig.field} with DROP action`);
}
testLogger.info('✓ STEP 3 PASSED: All 4 patterns linked to fields with DROP action');
// STEP 4: Verify all fields are DROPPED at query time (NO RE-INGESTION)
testLogger.info('STEP 4: Verify fields are DROPPED at query time (data unchanged in storage)');
const fieldsAfterDrop = patternsToTest.map(p => ({
fieldName: p.field,
shouldBeDropped: true
}));
await pm.sdrVerificationPage.verifyMultipleFields(pm.logsPage, testStreamName, fieldsAfterDrop, ingestMarker);
testLogger.info('✓ STEP 4 PASSED: All fields are DROPPED at query time');
testLogger.info('=== ✓ COMBINED QUERY TIME DROP TEST COMPLETED SUCCESSFULLY ===');
});
});