mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-03 16:10:21 +00:00
## 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>
165 lines
6.7 KiB
JavaScript
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 Hash - 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: `email_format_query_hash_${testRunId}`,
|
|
description: 'Email address validation pattern (for query time hash tests)',
|
|
pattern: '^[\\w\\.-]+@[\\w\\.-]+\\.\\w{2,}$',
|
|
field: 'user_email',
|
|
value: 'john.doe@example.com'
|
|
},
|
|
{
|
|
name: `us_phone_query_hash_${testRunId}`,
|
|
description: 'US phone number (10 digits) (for query time hash tests)',
|
|
pattern: '^\\d{10}$',
|
|
field: 'phone',
|
|
value: '5551234567'
|
|
},
|
|
{
|
|
name: `credit_card_query_hash_${testRunId}`,
|
|
description: 'Credit card number pattern (for query time hash tests)',
|
|
pattern: '^\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}$',
|
|
field: 'cc_number',
|
|
value: '1234 5678 9012 3456'
|
|
},
|
|
{
|
|
name: `ssn_query_hash_${testRunId}`,
|
|
description: 'Social Security Number pattern (for query time hash tests)',
|
|
pattern: '^\\d{3}-\\d{2}-\\d{4}$',
|
|
field: 'ssn',
|
|
value: '123-45-6789'
|
|
}
|
|
];
|
|
|
|
// Use unique stream name to avoid "stream being deleted" conflicts
|
|
const testStreamName = `sdr_query_hash_${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 hash test setup completed');
|
|
});
|
|
|
|
test("setup: cleanup patterns", {
|
|
tag: ['@sdr', '@cleanup', '@queryHash']
|
|
}, async ({ page }) => {
|
|
testLogger.info('=== SPEC-LEVEL CLEANUP: Cleaning up patterns for combined query hash 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 hash multiple fields at query time when patterns are applied", {
|
|
tag: ['@sdr', '@queryHash', '@all', '@combined']
|
|
}, async ({ page }, testInfo) => {
|
|
testInfo.annotations.push({ type: 'dependency', description: 'setup: cleanup patterns' });
|
|
|
|
testLogger.info('=== Testing combined HASH 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
|
|
// (hashed) inspect the SAME ingested batch — reuse one marker for both.
|
|
const ingestMarker = await pm.logsPage.ingestMultipleFields(testStreamName, dataToIngest);
|
|
|
|
// Verify all fields are visible without hashing
|
|
const fieldsBeforeHashing = patternsToTest.map(p => ({
|
|
fieldName: p.field,
|
|
shouldBeHashed: false
|
|
}));
|
|
await pm.sdrVerificationPage.verifyMultipleFields(pm.logsPage, testStreamName, fieldsBeforeHashing, 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 hashing
|
|
testLogger.info('STEP 3: Link all 4 patterns to their respective fields with HASH action');
|
|
|
|
for (const patternConfig of patternsToTest) {
|
|
await pm.streamAssociationPage.associatePatternWithStream(
|
|
testStreamName,
|
|
patternConfig.name,
|
|
'hash',
|
|
'query', // QUERY TIME - key difference from ingestion time
|
|
patternConfig.field
|
|
);
|
|
testLogger.info(`✓ Linked pattern ${patternConfig.name} to field ${patternConfig.field} with HASH action`);
|
|
}
|
|
|
|
testLogger.info('✓ STEP 3 PASSED: All 4 patterns linked to fields with HASH action');
|
|
|
|
// STEP 4: Verify all fields are HASHED at query time (NO RE-INGESTION)
|
|
testLogger.info('STEP 4: Verify fields are HASHED at query time (data unchanged in storage)');
|
|
|
|
const fieldsAfterHashing = patternsToTest.map(p => ({
|
|
fieldName: p.field,
|
|
shouldBeHashed: true
|
|
}));
|
|
await pm.sdrVerificationPage.verifyMultipleFields(pm.logsPage, testStreamName, fieldsAfterHashing, ingestMarker);
|
|
testLogger.info('✓ STEP 4 PASSED: All fields are HASHED at query time');
|
|
|
|
testLogger.info('=== ✓ COMBINED QUERY TIME HASH TEST COMPLETED SUCCESSFULLY ===');
|
|
});
|
|
});
|