mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-03 16:10:21 +00:00
test: reduce Logs-Builder-Basic e2e runtime by removing dead waits and duplicate setup (#13080)
## Reduce `Logs-Builder-Basic` e2e shard runtime — ~10 min → ~5 min
### 🎯 Result (measured on CI)
| Metric | Before | After | Saved |
|--------|--------|-------|-------|
| **`Logs-Builder-Basic` shard** | **10m 05s** | **5m 12s** | **~4m 53s
(≈48% faster)** |
The shard runtime is cut roughly in half. Most of the old time was fixed
sleeps, waits that always ran their full timeout, and duplicated
per-test setup — not actual test logic. **No test cases, steps, or
assertions were removed.**
### What we changed
| # | Area | Before | After | Saving |
|---|------|--------|-------|--------|
| 1 | `initQueryBuilderTest` setup | Logs page loaded twice (helper
`goto` + `selectStream` navigates again) | Navigate once | 1 page load ×
~41 tests |
| 2 | `waitForBuildTabLoaded` | Always waited up to 10s for editor to
fill, even when it's meant to be empty | Skip when SQL mode is off |
~10s dead wait on many tests |
| 3 | `enableSqlModeIfNeeded` | 2s watcher wait + 500ms sleep, even
where the watcher never fires | Gated to Build tab; waits on real signal
| ~2.5s × ~35 tests |
| 4 | `disableSqlModeIfNeeded` | Fixed 500–600ms sleeps | Deterministic
wait on editor update | Blind sleeps removed |
| 5 | `applyQueryButton` | Clicked Run blindly | Waits for button ready
first | Fewer early-click retries |
| 6 | `pagination.spec` | Fresh stream ingest + index-wait per test (9×)
| One ingest per worker, reused | 8 redundant ingests + waits |
| 7 | `pagination.spec` | Dead `waitForTimeout(2000)` per streaming test
| Removed | ~2s × 5 tests |
| 8 | `unflattened.spec` | Search run twice with 3s + 1s sleeps | Single
run | ~5s × ~8 calls |
| 9 | `unflattened` row scan | Fixed 3s sleep per row | Wait for content
to render | 15-row scan: ~97s → seconds |
| 10 | `unflattened.spec` | Blind sleep after ingest | Poll until stream
queryable | No over-waiting |
### Bonus
Items 2–5 live in the shared `logsPage` page object, so
`Logs-Builder-Advanced`, `Logs-Core`, and `Logs-Features` shards get
faster for free too.
### Notes
| Item | Detail |
|------|--------|
| `playwright.yml` | Unchanged — shard layout stays as-is |
| Assertions | Preserved everywhere (e.g. pagination still checks the
same record counts) |
| Validation | All 52 tests still discovered by `playwright --list`;
shard confirmed green on CI at 5m 12s |
This commit is contained in:
@@ -8259,19 +8259,26 @@ export class LogsPage {
|
||||
// Poll up to 2000ms for the Vue watcher chain to update the Monaco editor.
|
||||
// The watcher is async (reads buildDashboardPanelData, awaits onBuildQueryGenerated,
|
||||
// then Vue re-renders the editor prop) — a fixed 600ms was too short under CI load.
|
||||
await this.page.waitForFunction((selector) => {
|
||||
const host = document.querySelector(selector);
|
||||
if (!host) return false;
|
||||
const editors = window.monaco?.editor?.getEditors?.() ?? [];
|
||||
for (const ed of editors) {
|
||||
const node = ed.getDomNode?.();
|
||||
if (node && host.contains(node)) {
|
||||
const val = (ed.getValue() || '').toLowerCase().trim();
|
||||
return val.includes('select') && val.includes('from');
|
||||
// Only the Build tab has this watcher; on the main logs tab it returns early and the
|
||||
// editor never auto-populates, so waiting there just burns the full 2s before the
|
||||
// fallback below runs anyway. Skip straight to the fallback in that case.
|
||||
const onBuildTab = await this.page.locator(this.buildQueryPage)
|
||||
.isVisible({ timeout: 500 }).catch(() => false);
|
||||
if (onBuildTab) {
|
||||
await this.page.waitForFunction((selector) => {
|
||||
const host = document.querySelector(selector);
|
||||
if (!host) return false;
|
||||
const editors = window.monaco?.editor?.getEditors?.() ?? [];
|
||||
for (const ed of editors) {
|
||||
const node = ed.getDomNode?.();
|
||||
if (node && host.contains(node)) {
|
||||
const val = (ed.getValue() || '').toLowerCase().trim();
|
||||
return val.includes('select') && val.includes('from');
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, this.queryEditor, { timeout: 2000, polling: 100 }).catch(() => {});
|
||||
return false;
|
||||
}, this.queryEditor, { timeout: 2000, polling: 100 }).catch(() => {});
|
||||
}
|
||||
|
||||
// Check if the editor was updated (build tab watcher fired).
|
||||
// In the main logs tab the watcher returns early, so the editor stays in FTS mode.
|
||||
@@ -8291,8 +8298,21 @@ export class LogsPage {
|
||||
// now falls back to model.setValue() which bypasses the readOnly restriction.
|
||||
await this.setQueryEditorContent(sql);
|
||||
}
|
||||
// Allow Vue reactivity and CodeQueryEditor props.query watcher to propagate
|
||||
await this.page.waitForTimeout(500);
|
||||
// Wait for the CodeQueryEditor props.query watcher to actually apply the value
|
||||
// to Monaco instead of sleeping a fixed 500ms — deterministic and usually <300ms.
|
||||
await this.page.waitForFunction((selector) => {
|
||||
const host = document.querySelector(selector);
|
||||
if (!host) return false;
|
||||
const editors = window.monaco?.editor?.getEditors?.() ?? [];
|
||||
for (const ed of editors) {
|
||||
const node = ed.getDomNode?.();
|
||||
if (node && host.contains(node)) {
|
||||
const val = (ed.getValue() || '').toLowerCase().trim();
|
||||
return val.includes('select') && val.includes('from');
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, this.queryEditor, { timeout: 3000, polling: 100 }).catch(() => {});
|
||||
}
|
||||
testLogger.info('enableSqlModeIfNeeded: SQL mode enabled');
|
||||
}
|
||||
@@ -8313,7 +8333,22 @@ export class LogsPage {
|
||||
// watcher which calls onBuildQueryGenerated() → extractWhereClause() (async) →
|
||||
// searchObj.data.query = whereClause → QueryEditor prop watcher → Monaco setValue.
|
||||
await this._setSqlModeViaVue(false);
|
||||
await this.page.waitForTimeout(600);
|
||||
// Wait for the watcher chain to strip the SELECT from the editor instead of a fixed
|
||||
// 600ms sleep — resolves as soon as the editor updates, falls through on timeout to
|
||||
// the isStillSQL fallback below (same recovery path as before).
|
||||
await this.page.waitForFunction((selector) => {
|
||||
const host = document.querySelector(selector);
|
||||
if (!host) return true;
|
||||
const editors = window.monaco?.editor?.getEditors?.() ?? [];
|
||||
for (const ed of editors) {
|
||||
const node = ed.getDomNode?.();
|
||||
if (node && host.contains(node)) {
|
||||
const val = (ed.getValue() || '').toLowerCase().trim();
|
||||
return !(val.includes('select') && val.includes('from'));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, this.queryEditor, { timeout: 2000, polling: 100 }).catch(() => {});
|
||||
|
||||
// Verify the editor was updated. The Vue watcher chain is async and involves an
|
||||
// SQL-parser dynamic import, so it can exceed 600ms on first load or under CI load.
|
||||
@@ -8335,7 +8370,21 @@ export class LogsPage {
|
||||
// silently on read-only editors in the build tab's auto mode).
|
||||
await this.setQueryEditorContent('').catch(() => {});
|
||||
}
|
||||
await this.page.waitForTimeout(500);
|
||||
// Wait for the editor to actually reflect the cleared query instead of a
|
||||
// fixed 500ms sleep — deterministic, resolves as soon as propagation lands.
|
||||
await this.page.waitForFunction((selector) => {
|
||||
const host = document.querySelector(selector);
|
||||
if (!host) return true;
|
||||
const editors = window.monaco?.editor?.getEditors?.() ?? [];
|
||||
for (const ed of editors) {
|
||||
const node = ed.getDomNode?.();
|
||||
if (node && host.contains(node)) {
|
||||
const val = (ed.getValue() || '').toLowerCase().trim();
|
||||
return !(val.includes('select') && val.includes('from'));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, this.queryEditor, { timeout: 2000, polling: 100 }).catch(() => {});
|
||||
}
|
||||
testLogger.info('disableSqlModeIfNeeded: SQL mode disabled');
|
||||
}
|
||||
@@ -9300,6 +9349,16 @@ export class LogsPage {
|
||||
// for the Monaco editor inside [data-test="logs-search-bar-query-editor"] to have
|
||||
// non-empty content — this confirms that onBuildQueryGenerated() has been called and
|
||||
// queries[0].query is ready for any Auto→Custom mode switch.
|
||||
//
|
||||
// With SQL mode OFF the editor holds only the WHERE clause and is often legitimately
|
||||
// empty, so the wait below would always burn its full 10s timeout before the catch
|
||||
// fires. Read sqlMode from Vue state and skip Phase 3 on an explicit `false`; if the
|
||||
// state is unreachable (null) fall through to the wait as before.
|
||||
const sqlModeOn = await this._mutateSearchObj((searchObj) => searchObj.meta.sqlMode === true);
|
||||
if (sqlModeOn === false) {
|
||||
testLogger.info('Build tab loaded (SQL mode off — query editor population not expected)');
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
await this.page.waitForFunction(
|
||||
(editorSelector) => {
|
||||
|
||||
@@ -243,10 +243,15 @@ class UnflattenedPage {
|
||||
await this.page
|
||||
.locator('[data-test="logs-search-result-detail-dialog"][data-state="open"]')
|
||||
.waitFor({ state: 'visible', timeout: 10000 });
|
||||
// JSON content renders asynchronously after the drawer opens.
|
||||
// Hard wait gives Vue time to populate the field list before callers
|
||||
// probe for specific keys like _o2_id.
|
||||
await this.page.waitForTimeout(3000);
|
||||
// JSON content renders asynchronously after the drawer opens. Wait for the
|
||||
// first detail key to actually render instead of a fixed 3s sleep — the keys
|
||||
// appearing IS the signal callers need before probing for _o2_id, and the
|
||||
// fixed sleep made findRowWithO2Id's 15-row scan cost ~1.5 minutes per
|
||||
// attempt (the dominant share of the 5-minute-timeout flake on CI).
|
||||
await this.allLogDetailKeys
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 10000 })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,7 +319,11 @@ class UnflattenedPage {
|
||||
*/
|
||||
async waitForO2IdQueryable({ streamName = 'e2e_automate', timeout = 90000, pollInterval = 2000 } = {}) {
|
||||
const { getAuthHeaders, getOrgIdentifier } = require('../../playwright-tests/utils/cloud-auth.js');
|
||||
const baseUrl = (process.env.ZO_BASE_URL || '').replace(/\/$/, '');
|
||||
// Prefer INGESTION_URL like waitForStreamAvailable does: on CI both point at the
|
||||
// same server, but in local setups ZO_BASE_URL can be a frontend dev server that
|
||||
// does not serve /api/* (it returns an HTML hint page with HTTP 200, which made
|
||||
// this gate silently poll uselessly for its entire budget).
|
||||
const baseUrl = (process.env.INGESTION_URL || process.env.ZO_BASE_URL || '').replace(/\/$/, '');
|
||||
const orgId = getOrgIdentifier();
|
||||
const headers = getAuthHeaders();
|
||||
const deadline = Date.now() + timeout;
|
||||
@@ -342,8 +351,14 @@ class UnflattenedPage {
|
||||
}
|
||||
);
|
||||
if (resp.ok()) {
|
||||
const body = await resp.json().catch(() => ({}));
|
||||
const hits = Array.isArray(body.hits) ? body.hits : [];
|
||||
const body = await resp.json().catch(() => null);
|
||||
if (body === null) {
|
||||
// HTTP 200 but not JSON — almost certainly the wrong endpoint
|
||||
// (e.g. a frontend dev server). Surface it instead of silently
|
||||
// burning the whole gate budget.
|
||||
console.warn('waitForO2IdQueryable: response OK but not JSON — wrong endpoint? (will retry)');
|
||||
}
|
||||
const hits = Array.isArray(body?.hits) ? body.hits : [];
|
||||
if (hits.some((h) => h && Object.prototype.hasOwnProperty.call(h, '_o2_id'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,20 +15,29 @@ function generateRandomStreamName() {
|
||||
|
||||
test.describe("Pagination for logs", () => {
|
||||
let pageManager;
|
||||
// One stream per worker process: every test asserts pagination counts against the
|
||||
// record count of a single ingestion, so ingesting the same fixture once and reusing
|
||||
// the stream across the worker's tests yields identical validations while removing
|
||||
// a redundant ingest + index-wait from every test. Module state is per worker
|
||||
// (workers are separate processes), so parallel workers stay isolated.
|
||||
let streamName;
|
||||
let streamReady = false;
|
||||
|
||||
const orgId = process.env.ORGNAME || "default";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
|
||||
streamName = generateRandomStreamName();
|
||||
pageManager = new PageManager(page);
|
||||
await pageManager.loginPage.gotoLoginPage();
|
||||
await pageManager.loginPage.loginAsInternalUser();
|
||||
await pageManager.loginPage.login(); // Login as root user
|
||||
await pageManager.ingestionPage.ingestionMultiOrgStream(orgId, streamName);
|
||||
// Wait for stream to be indexed — cloud can take 30-60s
|
||||
await pageManager.logsPage.waitForStreamAvailable(streamName, 120000, 3000);
|
||||
if (!streamReady) {
|
||||
streamName = generateRandomStreamName();
|
||||
await pageManager.ingestionPage.ingestionMultiOrgStream(orgId, streamName);
|
||||
// Wait for stream to be indexed — cloud can take 30-60s
|
||||
await pageManager.logsPage.waitForStreamAvailable(streamName, 120000, 1000);
|
||||
streamReady = true;
|
||||
}
|
||||
});
|
||||
|
||||
test("HTTP Pagination for running query to validate WHERE match_all('2022-12-27T14:11:27Z INFO zinc_enl')", { tag: ['@pagination', '@functional', '@P1'] }, async ({ page }) => {
|
||||
@@ -103,7 +112,6 @@ test.describe("Pagination for logs", () => {
|
||||
test("Enable Streaming for running query to validate WHERE match_all('2022-12-27T14:11:27Z INFO zinc_enl')", { tag: ['@pagination', '@streaming', '@functional', '@P1'] }, async ({ page }) => {
|
||||
|
||||
// Streaming is enabled via ZO_STREAMING_ENABLED env var — no need to toggle
|
||||
await page.waitForTimeout(2000);
|
||||
await pageManager.logsPage.navigateToLogs();
|
||||
await pageManager.logsPage.selectIndexStream(streamName);
|
||||
testLogger.debug('Stream name generated', { streamName });
|
||||
@@ -122,7 +130,6 @@ test.describe("Pagination for logs", () => {
|
||||
test("Enable Streaming for running query to validate WHERE match_all('zin*')", { tag: ['@pagination', '@streaming', '@functional', '@P1'] }, async ({ page }) => {
|
||||
|
||||
// Streaming is enabled via ZO_STREAMING_ENABLED env var — no need to toggle
|
||||
await page.waitForTimeout(2000);
|
||||
await pageManager.logsPage.navigateToLogs();
|
||||
await pageManager.logsPage.selectIndexStream(streamName);
|
||||
testLogger.debug('Stream name generated', { streamName });
|
||||
@@ -141,7 +148,6 @@ test.describe("Pagination for logs", () => {
|
||||
test("Enable Streaming for running query to validate WHERE match_all('2022-12-27T1*')", { tag: ['@pagination', '@streaming', '@functional', '@P1'] }, async ({ page }) => {
|
||||
|
||||
// Streaming is enabled via ZO_STREAMING_ENABLED env var — no need to toggle
|
||||
await page.waitForTimeout(2000);
|
||||
await pageManager.logsPage.navigateToLogs();
|
||||
await pageManager.logsPage.selectIndexStream(streamName);
|
||||
testLogger.debug('Stream name generated', { streamName });
|
||||
@@ -160,7 +166,6 @@ test.describe("Pagination for logs", () => {
|
||||
test("Enable Streaming for running query to validate WHERE match_all('2022-12-27T14:11:2*')", { tag: ['@pagination', '@streaming', '@functional', '@P1'] }, async ({ page }) => {
|
||||
|
||||
// Streaming is enabled via ZO_STREAMING_ENABLED env var — no need to toggle
|
||||
await page.waitForTimeout(2000);
|
||||
await pageManager.logsPage.navigateToLogs();
|
||||
await pageManager.logsPage.selectIndexStream(streamName);
|
||||
testLogger.debug('Stream name generated', { streamName });
|
||||
@@ -179,7 +184,6 @@ test.describe("Pagination for logs", () => {
|
||||
test("Enable Streaming for running query to validate pagination is not visible WHERE match_all('2022-12-27T14:11:27Z INFO zinc_enl') limit`", { tag: ['@pagination', '@streaming', '@functional', '@P1'] }, async ({ page }) => {
|
||||
|
||||
// Streaming is enabled via ZO_STREAMING_ENABLED env var — no need to toggle
|
||||
await page.waitForTimeout(2000);
|
||||
await pageManager.logsPage.navigateToLogs();
|
||||
await pageManager.logsPage.selectIndexStream(streamName);
|
||||
testLogger.debug('Stream name generated', { streamName });
|
||||
|
||||
@@ -16,23 +16,38 @@ test.describe("Unflattened testcases", () => {
|
||||
return text.replace(/[^\x00-\x7F]/g, " ");
|
||||
}
|
||||
async function applyQueryButton(page) {
|
||||
// After stream selection, an auto-search may be in progress (button shows
|
||||
// "Cancel"). Clicking refresh while a search is running just cancels it
|
||||
// without starting a new one. We click twice: first to cancel any
|
||||
// in-flight search, then again to start a fresh one.
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// First click — cancels any in-progress auto-search.
|
||||
await pageManager.logsPage.runQueryAndWaitForResults();
|
||||
|
||||
// Second click — reliably starts a new search now that no search is running.
|
||||
await page.waitForTimeout(1000);
|
||||
// runQueryAndWaitForResults already handles an in-flight auto-search internally:
|
||||
// it waits for the button to exit the "Cancel" state before clicking, and if the
|
||||
// state never clears it cancels and re-clicks Run itself. The old double-click
|
||||
// wrapper (3s sleep + run + 1s sleep + run) duplicated that logic and added ~5s
|
||||
// of fixed sleep to every call.
|
||||
await pageManager.logsPage.runQueryAndWaitForResults();
|
||||
|
||||
// 2-minute timeout: cloud re-indexing after Store Original Data changes takes longer than the default 30s
|
||||
await pageManager.logsPage.waitForSearchResults(120000);
|
||||
}
|
||||
|
||||
// Re-run the log search from a FRESH page load so the row scan below sees a
|
||||
// complete, freshly-ordered result set. Re-clicking Run in place does not
|
||||
// recover a stale/partial streaming result set (ZO_STREAMING_ENABLED delivers
|
||||
// hits over a WebSocket that can lag the HTTP _search readiness gate) — the
|
||||
// only reliable recovery is a new navigation + query, which is exactly what the
|
||||
// whole-test Playwright retry does when it succeeds. This reproduces that here
|
||||
// without re-ingesting, so it stays cheap. Pass sqlSelectAll for the quick-mode
|
||||
// test, whose intent is specifically a `SELECT *` query (a plain reload would
|
||||
// otherwise fall back to the quick-mode interesting-fields-only query).
|
||||
async function runFreshLogSearch(page, { sqlSelectAll = false } = {}) {
|
||||
await page.goto(
|
||||
`${process.env.ZO_BASE_URL}/web/logs?org_identifier=${getOrgIdentifier()}&stream_type=logs&stream=e2e_automate`
|
||||
);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
if (sqlSelectAll) {
|
||||
await pageManager.logsPage.typeQuery('SELECT * FROM "e2e_automate" ORDER BY _timestamp DESC');
|
||||
await pageManager.logsPage.waitForQueryEditorValue('ORDER BY _timestamp DESC');
|
||||
}
|
||||
await applyQueryButton(page);
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
pageManager = new PageManager(page);
|
||||
if (isCloudEnvironment()) {
|
||||
@@ -42,11 +57,12 @@ test.describe("Unflattened testcases", () => {
|
||||
await pageManager.loginPage.loginAsInternalUser();
|
||||
await pageManager.loginPage.login();
|
||||
}
|
||||
await page.waitForTimeout(2000);
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await ingestion(page);
|
||||
// Wait for data to be indexed before navigating to logs
|
||||
await page.waitForTimeout(2000);
|
||||
// Wait for the stream to actually be queryable instead of a blind 2s sleep —
|
||||
// deterministic (usually instant once the stream exists) and more tolerant on
|
||||
// slow CI runners than a fixed pause.
|
||||
await pageManager.logsPage.waitForStreamAvailable('e2e_automate', 30000, 1000);
|
||||
|
||||
// Check and disable Store Original Data if it's enabled.
|
||||
// Navigate directly to the streams URL with the correct org — clicking the
|
||||
@@ -162,11 +178,12 @@ test.describe("Unflattened testcases", () => {
|
||||
await applyQueryButton(page);
|
||||
testLogger.info('Search query applied, logs should now contain _o2_id field');
|
||||
|
||||
testLogger.info('Searching log rows for _o2_id field (iterates first 10 rows per attempt)');
|
||||
testLogger.info('Searching log rows for _o2_id field (iterates first 15 rows per attempt)');
|
||||
let o2idFound = false;
|
||||
// The backend readiness gate above already guarantees _o2_id is queryable, so
|
||||
// a miss here is only UI/render lag — re-run the query (cheap) rather than
|
||||
// re-ingesting (the old loop's re-ingest + 5s sleep is what ballooned wall-time).
|
||||
// a miss here is UI/streaming lag — recover with a FRESH log search (reload +
|
||||
// re-run) rather than re-ingesting. Re-clicking Run in place does not recover a
|
||||
// stale/partial streaming result set; a fresh navigation does (see runFreshLogSearch).
|
||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||
const matchedRow = await pageManager.unflattenedPage.findRowWithO2Id(15);
|
||||
if (matchedRow !== -1) {
|
||||
@@ -175,10 +192,10 @@ test.describe("Unflattened testcases", () => {
|
||||
o2idFound = true;
|
||||
break;
|
||||
}
|
||||
testLogger.warn(`_o2_id not found in first 10 rows on attempt ${attempt}, re-running query`);
|
||||
testLogger.warn(`_o2_id not found in first 15 rows on attempt ${attempt}, re-running query from a fresh page`);
|
||||
await pageManager.unflattenedPage.closeLogDetailDrawerIfOpen();
|
||||
if (attempt < 3) {
|
||||
await applyQueryButton(page);
|
||||
await runFreshLogSearch(page);
|
||||
}
|
||||
}
|
||||
if (!o2idFound) {
|
||||
@@ -322,14 +339,22 @@ test.describe("Unflattened testcases", () => {
|
||||
testLogger.info('Replacing query with SELECT * FROM "e2e_automate" ORDER BY _timestamp DESC');
|
||||
await pageManager.logsPage.typeQuery('SELECT * FROM "e2e_automate" ORDER BY _timestamp DESC');
|
||||
|
||||
// Wait for Monaco to commit the typed query to the Vue store before clicking Run.
|
||||
// Without this the click can race the editor debounce and re-run the PREVIOUS
|
||||
// (quick-mode fields-only) query, whose hits never contain _o2_id — dooming the
|
||||
// row scan below and burning the whole 5-minute test budget (CI flake).
|
||||
await pageManager.logsPage.waitForQueryEditorValue('ORDER BY _timestamp DESC');
|
||||
|
||||
testLogger.info('Executing SELECT * query to fetch fresh data with _o2_id');
|
||||
await applyQueryButton(page);
|
||||
|
||||
testLogger.info('Searching log rows for _o2_id field (iterates first 10 rows per attempt)');
|
||||
// With ORDER BY _timestamp DESC the newest rows come first. The backend
|
||||
// readiness gate above already guarantees _o2_id is queryable, so a miss here
|
||||
// is only UI/render lag — re-run the query (cheap) rather than re-ingesting
|
||||
// (the old loop's re-ingest + 5s sleep is what ballooned wall-time on CI).
|
||||
testLogger.info('Searching log rows for _o2_id field (iterates first 15 rows per attempt)');
|
||||
// With ORDER BY _timestamp DESC the newest rows come first (the fixture carries
|
||||
// no _timestamp, so the Store-Original-Data re-ingest is genuinely newest). The
|
||||
// backend readiness gate above guarantees _o2_id is queryable, so a miss here is
|
||||
// UI/streaming lag — recover with a FRESH SELECT * search (reload + re-type +
|
||||
// re-run) rather than re-ingesting. Re-clicking Run in place does not recover a
|
||||
// stale/partial streaming result set; a fresh navigation does.
|
||||
let o2idFound = false;
|
||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||
const matchedRow = await pageManager.unflattenedPage.findRowWithO2Id(15);
|
||||
@@ -339,7 +364,7 @@ test.describe("Unflattened testcases", () => {
|
||||
o2idFound = true;
|
||||
break;
|
||||
}
|
||||
testLogger.warn(`_o2_id not found in first 10 rows on attempt ${attempt}, re-running query`);
|
||||
testLogger.warn(`_o2_id not found in first 15 rows on attempt ${attempt}, re-running query from a fresh page`);
|
||||
if (attempt === 3) {
|
||||
try {
|
||||
const allKeys = await pageManager.unflattenedPage.allLogDetailKeys.allTextContents();
|
||||
@@ -350,7 +375,7 @@ test.describe("Unflattened testcases", () => {
|
||||
break;
|
||||
}
|
||||
await pageManager.unflattenedPage.closeLogDetailDrawerIfOpen();
|
||||
await applyQueryButton(page);
|
||||
await runFreshLogSearch(page, { sqlSelectAll: true });
|
||||
}
|
||||
if (!o2idFound) {
|
||||
throw new Error('Failed to find _o2_id field in log details after 3 attempts');
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const testLogger = require('./test-logger.js');
|
||||
const logData = require('../../fixtures/log.json');
|
||||
const logsdata = require('../../../test-data/logs_data.json');
|
||||
const { getOrgIdentifier, getAuthHeaders } = require('./cloud-auth.js');
|
||||
|
||||
@@ -25,6 +24,19 @@ async function ingestForQueryBuilderTest(request, streamName = "e2e_automate") {
|
||||
}
|
||||
|
||||
async function applyQueryButton(pm) {
|
||||
// Stream selection fires an auto-search that leaves the Run button disabled,
|
||||
// aria-busy, or swapped to "Cancel" while in flight. Clicking blind during that
|
||||
// window fails with "Element is not visible" — wait for the button to be ready
|
||||
// first. The catch falls through to the click, which then reports loudly.
|
||||
await pm.page.waitForFunction((selector) => {
|
||||
const el = document.querySelector(selector);
|
||||
if (!el) return false;
|
||||
if (el.getAttribute('aria-busy') === 'true') return false;
|
||||
if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') return false;
|
||||
if (((el.textContent || '').trim()).includes('Cancel')) return false;
|
||||
return el.offsetParent !== null;
|
||||
}, pm.logsPage.queryButton, { timeout: 60000, polling: 100 }).catch(() => {});
|
||||
|
||||
const searchPromise = pm.page.waitForResponse(
|
||||
response => response.url().includes('/_search') && response.status() === 200,
|
||||
{ timeout: 60000 }
|
||||
@@ -51,12 +63,19 @@ async function setupQueryAndSwitchToBuild(pm, page, query) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Full init: navigate, select stream, run initial match_all query.
|
||||
* Full init: select stream (selectStream navigates to the logs page itself, so no
|
||||
* separate page.goto here — that would just load the same page twice), then run
|
||||
* the initial match_all query.
|
||||
* Ingestion is handled by ingestForQueryBuilderTest in beforeAll.
|
||||
*
|
||||
* The applyQueryButton call is NOT optional setup: selecting a stream fires an
|
||||
* auto-search, and waiting for its /_search response here absorbs it before the
|
||||
* test body runs. Skipping it lets that response land mid-test and overwrite the
|
||||
* test's query state (observed as table/metric chart-type tests flaking to the
|
||||
* default bar chart under load).
|
||||
*/
|
||||
async function initQueryBuilderTest(page, pm) {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.goto(`${logData.logsUrl}?org_identifier=${getOrgIdentifier()}`);
|
||||
await pm.logsPage.selectStream("e2e_automate");
|
||||
await applyQueryButton(pm);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user