From 0929fa5678334cd2eefd9937c7d25e48dd891fa5 Mon Sep 17 00:00:00 2001 From: Shrinath Rao Date: Sat, 30 May 2026 08:01:44 +0530 Subject: [PATCH] =?UTF-8?q?test:=20Sentinel=20audit=20fixes=20+=20parallel?= =?UTF-8?q?ize=20Metrics=20E2E=20shard=20(~22=20min=20=E2=86=92=20~5-7=20m?= =?UTF-8?q?in)=20(#12367)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Two independent improvements to the Playwright E2E test suite. --- ### Part 1 — Sentinel audit fixes (original PR scope) Fixes from the Sentinel audit review of PR #12358 (selector drift stabilization). - **Dead code removal**: delete `clickJobID()` from `logsPageEP.js` (unreachable code containing `console.log`); remove unused `chromium` import - **Critical fix**: replace `console.log`/`console.error` in `decryptLogSQL()` with `testLogger` equivalents - **POM compliance**: replace raw `page.locator()` call in `alerts-ui-operations.spec.js` `beforeEach` with `pm.alertsPage.isIncidentsFeatureEnabled()`; add that method to `alertsPage.js` - **POM compliance**: move per-test `const pm = new PageManager(page)` into `beforeEach` in `service-graph.spec.js`; replace raw `page.waitForLoadState()` with `pm.serviceGraphPage.waitForGraphReload()`; add `waitForGraphReload()` to `serviceGraphPage.js` - **Inline selector cleanup**: move metrics tab inline string literals in `serviceGraphPage.js` into constructor properties - **Tagging**: add `@cipherKeys/@enterprise/@P1/@P2/@negative/@validation` to all 6 tests in `cipherKeys.spec.js` (were untagged); add `@P1/@P2` to all 5 tests in `regexPatternManagement.spec.js` - **ESM fix**: correct `export const path = require('path')` → `import path from 'path'` in `logoManagementPage.js` - **Deduplication**: consolidate `createFirstFunction`/`createSecondFunction` into shared `createFunction(name, vrlCode)` in `pipelinesEP.js` --- ### Part 2 — Parallelize Metrics E2E shard **Problem**: The `e2e / Metrics` CI shard was consistently the slowest job at ~22 minutes per PR run, while all other shards complete in ~5-7 minutes. **Root cause**: 9 of 11 Metrics spec files used `test.describe.configure({ mode: 'serial' })` because they shared a describe-scope `let pm` variable reassigned in `beforeEach`. This forced every test in those files onto a single Playwright worker, eliminating all parallelism despite `fullyParallel: true` and 5 workers being configured. **Fix applied to all 9 affected files** (`metrics.spec.js`, `metrics-config.spec.js`, `metrics-config-tabs.spec.js`, `metrics-visualizations.spec.js`, `metrics-promql-query-persistence.spec.js`, `metrics-table-column-order.spec.js`, `metrics-queries.spec.js`, `metrics-aggregations.spec.js`, `metrics-advanced.spec.js`): - Remove `test.describe.configure({ mode: 'serial' })` - Replace shared `let pm` + `beforeEach` pattern with a `setupTest(page, testInfo)` helper that creates and returns a fresh `PageManager` per test — the same pattern already used in `metrics-promql-builder.spec.js` (the one file that was already parallel) - `metrics-table-column-order.spec.js`: pass `pm` explicitly into `setupTableChart(pm, page, mode)` since it can no longer close over a describe-scope variable - Split consolidated mega-tests (for-loops over multiple independent scenarios) in `metrics-queries`, `metrics-aggregations`, and `metrics-advanced` into individual `test()` blocks so Playwright can distribute them across workers **No test assertions, selectors, query logic, or validation changed.** | File | Tests before | Tests after | |---|---|---| | `metrics.spec.js` | 10 (serial) | 10 (parallel) | | `metrics-config.spec.js` | 2 (serial) | 2 (parallel) | | `metrics-config-tabs.spec.js` | 3 (serial) | 3 (parallel) | | `metrics-visualizations.spec.js` | 4 (serial) | 4 (parallel) | | `metrics-promql-query-persistence.spec.js` | 6 (serial) | 6 (parallel) | | `metrics-table-column-order.spec.js` | 20 (serial) | 20 (parallel) | | `metrics-queries.spec.js` | 4 mega-tests (serial) | 12 individual (parallel) | | `metrics-aggregations.spec.js` | 3 mega-tests (serial) | 10 individual (parallel) | | `metrics-advanced.spec.js` | 4 mega-tests (serial) | 12 individual (parallel) | **Expected impact**: ~1300s (~22 min) → ~300-400s (~5-7 min) wall-clock time. --- ## Test plan - [ ] All 40 tests in the 4 Sentinel-audit spec files enumerate cleanly via `npx playwright test --list` - [ ] Tag filters `@cipherKeys`, `@P2`, `@sdr` match expected tests - [ ] All Metrics spec files enumerate cleanly with no serial mode warnings - [ ] Metrics shard CI time drops to ~5-7 min on next PR run 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 --- .../pages/alertsPages/alertsPage.js | 15 + .../pages/generalPages/logoManagementPage.js | 3 +- tests/ui-testing/pages/logsPages/logsPage.js | 12 + .../ui-testing/pages/logsPages/logsPageEP.js | 49 +- .../pages/pipelinesPages/pipelinesEP.js | 31 +- .../pages/tracesPages/serviceGraphPage.js | 31 +- .../Alerts/alerts-ui-operations.spec.js | 8 +- .../GeneralTests/cipherKeys.spec.js | 24 +- .../Logs/logsAnalyzeDimensions.spec.js | 1 + .../Metrics/metrics-advanced.spec.js | 346 +++++---- .../Metrics/metrics-aggregations.spec.js | 414 +++++----- .../Metrics/metrics-config-tabs.spec.js | 31 +- .../Metrics/metrics-config.spec.js | 22 +- .../Metrics/metrics-promql-builder.spec.js | 2 +- .../metrics-promql-query-persistence.spec.js | 708 +++++++++--------- .../Metrics/metrics-queries.spec.js | 340 +++++---- .../metrics-table-column-order.spec.js | 118 +-- .../Metrics/metrics-visualizations.spec.js | 28 +- .../playwright-tests/Metrics/metrics.spec.js | 46 +- .../SDR/regexPatternManagement.spec.js | 10 +- .../Traces/service-graph.spec.js | 19 +- 21 files changed, 1184 insertions(+), 1074 deletions(-) diff --git a/tests/ui-testing/pages/alertsPages/alertsPage.js b/tests/ui-testing/pages/alertsPages/alertsPage.js index f421884bac..d5f2aa5ddb 100644 --- a/tests/ui-testing/pages/alertsPages/alertsPage.js +++ b/tests/ui-testing/pages/alertsPages/alertsPage.js @@ -1221,6 +1221,21 @@ export class AlertsPage { * Note: Incidents menu item depends on service_graph_enabled config from the server. * The menu item may take time to render while the config API response is processed. */ + + /** + * Returns true if the incidents sidebar menu item becomes visible within the given timeout. + * Use this in beforeEach to skip enterprise tests on OSS environments. + * @param {number} timeout - ms to wait (default 5000) + */ + async isIncidentsFeatureEnabled(timeout = 5000) { + try { + await this.page.locator(this.locators.incidentsMenuItem).waitFor({ state: 'visible', timeout }); + return true; + } catch { + return false; + } + } + async navigateToIncidentsPage() { testLogger.info('Navigating to Incidents page'); // Wait for the incidents menu item to be available (may take time for config to load) diff --git a/tests/ui-testing/pages/generalPages/logoManagementPage.js b/tests/ui-testing/pages/generalPages/logoManagementPage.js index 3bea23122d..511edd4e33 100644 --- a/tests/ui-testing/pages/generalPages/logoManagementPage.js +++ b/tests/ui-testing/pages/generalPages/logoManagementPage.js @@ -1,6 +1,5 @@ import { expect } from '@playwright/test'; - -export const path = require('path'); +import path from 'path'; export class LogoManagementPage { diff --git a/tests/ui-testing/pages/logsPages/logsPage.js b/tests/ui-testing/pages/logsPages/logsPage.js index e1b3367ee3..aee3f2ef5c 100644 --- a/tests/ui-testing/pages/logsPages/logsPage.js +++ b/tests/ui-testing/pages/logsPages/logsPage.js @@ -6463,6 +6463,18 @@ export class LogsPage { } } + /** + * Wait for actual result rows to appear in the table. + * The table container becomes visible before Vue populates queryResults.hits, which gates + * the analyze button v-if. Waiting for a row ensures hits.length > 0 is true in the + * reactive store before callers check for the analyze button or row-dependent UI. + */ + async waitForSearchResultRows(timeout = 20000) { + await this.page.locator(`${this.logsTable} tbody tr`).first() + .waitFor({ state: 'visible', timeout }); + testLogger.info('waitForSearchResultRows: at least one result row is visible'); + } + /** * Wait for SQL mode to be active after switching */ diff --git a/tests/ui-testing/pages/logsPages/logsPageEP.js b/tests/ui-testing/pages/logsPages/logsPageEP.js index 0c213468a0..89c0f4fb5a 100644 --- a/tests/ui-testing/pages/logsPages/logsPageEP.js +++ b/tests/ui-testing/pages/logsPages/logsPageEP.js @@ -1,6 +1,6 @@ // logsPageEP.js import { expect } from '@playwright/test'; -const { chromium } = require('playwright'); +const testLogger = require('../../playwright-tests/utils/test-logger.js'); export class LogsPageEP { constructor(page) { @@ -96,49 +96,6 @@ async queryJobSearch() { -async clickJobID () { - const { getOrgIdentifier } = require('../../playwright-tests/utils/cloud-auth.js'); - const orgId = getOrgIdentifier(); - - // Intercept the network request and capture the response - await this.page.route( - `${process.env["ZO_BASE_URL"]}/api/${orgId}/search_jobs?type=logs&search_type=UI&use_cache=true`, - async (route) => { - const response = await route.continue(); - const responseBody = await response.body(); - const jsonResponse = JSON.parse(responseBody.toString()); - - // Assuming the ID is in the response JSON - const idJob = jsonResponse.trace_id; // Adjust this according to your response structure - console.log("Job ID Created", idJob); - - // Use the ID to construct the selector - const rowSelector = `tr[data-test="search-scheduler-table-${idJob}-row"]`; - const cancelBtnSelector = `${rowSelector} [data-test="search-scheduler-cancel-btn"]`; - const restartBtnSelector = `${rowSelector} [data-test="search-scheduler-restart-btn"]`; - - // Wait for the row to be visible before clicking - await this.page.waitForSelector(rowSelector); - - // Click the cancel button - await this.page.locator(cancelBtnSelector).click(); - await this.page.waitForTimeout(2000); - // Click the confirm button - await this.page.locator('[data-test="confirm-dialog"] [data-test="o-dialog-primary-btn"]').click(); - await this.page.waitForTimeout(2000); - // Click the restart button - await this.page.locator(restartBtnSelector).click(); - await this.page.waitForTimeout(2000); - // Continue with your automation steps... - } - ) - -} - - - - - async searchSchedulerInvalid() { // OInput: use the inner native input (-field suffix) for fill/press operations @@ -174,12 +131,12 @@ async decryptLogSQL(cipherName) { // Wait for the locator to be visible and enabled const editorLocator = this.page.locator('[data-test="logs-search-bar-query-editor-input"]'); await editorLocator.waitFor({ state: 'visible', timeout: 10000 }); - console.log("SQL Query", sqlQuery); + testLogger.info(`SQL Query: ${sqlQuery}`); // Fill the query editor with the constructed SQL query await editorLocator.fill(sqlQuery); await this.page.waitForTimeout(5000); } catch (error) { - console.error('Error during SQL decryption process:', error); + testLogger.error(`Error during SQL decryption process: ${error}`); throw error; // Re-throw the error if needed for further handling } } diff --git a/tests/ui-testing/pages/pipelinesPages/pipelinesEP.js b/tests/ui-testing/pages/pipelinesPages/pipelinesEP.js index 2e11b7f58a..90897ed834 100644 --- a/tests/ui-testing/pages/pipelinesPages/pipelinesEP.js +++ b/tests/ui-testing/pages/pipelinesPages/pipelinesEP.js @@ -131,42 +131,25 @@ export class PipelinesEP { await this.page.keyboard.type(functionName, { delay: 30 }); } - async createFirstFunction(functionName) { - // Wait for the button to be visible + async createFunction(functionName, vrlCode) { await this.createFunctionButton.waitFor({ state: 'visible' }); - // Click the button await this.createFunctionButton.click(); await this._fillFunctionName(functionName); - // Set VRL function code via Monaco API (more reliable than keyboard typing) - await this.setMonacoEditorValue('.a=41'); - + await this.setMonacoEditorValue(vrlCode); await this.page.locator(this.saveFunctionButton).waitFor({ state: 'visible', timeout: 10000 }); await this.page.locator(this.saveFunctionButton).click(); - // Wait for success notification instead of a fixed timeout await expect( this.page.locator(this.notifyContainer).filter({ hasText: 'Function saved successfully' }).first() ).toBeVisible({ timeout: 15000 }); - // Wait for function list to reload await this.createFunctionButton.waitFor({ state: 'visible', timeout: 10000 }); } - async createSecondFunction(functionName) { - // Wait for the button to be visible - await this.createFunctionButton.waitFor({ state: 'visible' }); - // Click the button - await this.createFunctionButton.click(); - await this._fillFunctionName(functionName); - // Set VRL function code via Monaco API (more reliable than keyboard typing) - await this.setMonacoEditorValue('.test=2'); + async createFirstFunction(functionName) { + await this.createFunction(functionName, '.a=41'); + } - await this.page.locator(this.saveFunctionButton).waitFor({ state: 'visible', timeout: 10000 }); - await this.page.locator(this.saveFunctionButton).click(); - // Wait for success notification instead of a fixed timeout - await expect( - this.page.locator(this.notifyContainer).filter({ hasText: 'Function saved successfully' }).first() - ).toBeVisible({ timeout: 15000 }); - // Wait for function list to reload - await this.createFunctionButton.waitFor({ state: 'visible', timeout: 10000 }); + async createSecondFunction(functionName) { + await this.createFunction(functionName, '.test=2'); } diff --git a/tests/ui-testing/pages/tracesPages/serviceGraphPage.js b/tests/ui-testing/pages/tracesPages/serviceGraphPage.js index 00b68428fe..25a621133d 100644 --- a/tests/ui-testing/pages/tracesPages/serviceGraphPage.js +++ b/tests/ui-testing/pages/tracesPages/serviceGraphPage.js @@ -52,6 +52,14 @@ export class ServiceGraphPage { this.podsPanel = '[data-test="service-graph-side-panel-pods"]'; this.podsTable = '[data-test="service-graph-side-panel-pods-table"]'; + // ===== TELEMETRY CORRELATION (Metrics tab) ===== + this.metricsTab = '[data-test="service-graph-node-panel-tab-metrics"]'; + this.metricsPanel = '[data-test="service-graph-side-panel-metrics"]'; + this.metricsLoadingIndicator = '[data-test="service-graph-side-panel-metrics-loading"]'; + this.metricsDashboard = '[data-test="service-graph-side-panel-metrics-dashboard"]'; + this.metricsError = '[data-test="service-graph-side-panel-metrics-error"]'; + this.metricsEmpty = '[data-test="service-graph-side-panel-metrics-empty"]'; + // ===== TELEMETRY CORRELATION DIALOG ===== this.correlationDashboardClose = '[data-test="correlation-dashboard-close"]'; this.correlationDashboardCard = '[data-test*="correlation-dashboard"]'; @@ -100,6 +108,10 @@ export class ServiceGraphPage { await this.page.locator(this.refreshButton).click(); } + async waitForGraphReload() { + await this.page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {}); + } + async switchToGraphView() { await this.page.locator(this.graphViewTab).click(); // OToggleGroupItem uses inheritAttrs:false + v-bind="$attrs" on the inner Reka UI