mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
## What & why Sentinel (Council of Agents) audit-driven **Page Object Model compliance** across the Playwright suite. Many specs (a lot added recently, some AI-generated) had raw `page.locator(...)` / `page.getBy*(...)` selectors directly in spec files — including inside `expect(...)` — which the Sentinel flags as CRITICAL. This PR relocates those selectors into their page objects and calls page-object methods/getters instead. A full audit of **all 256 specs** was generated first (stored locally under `tests/ui-testing/MD_Files/sentinel-audit-2026-08-10/`, which is gitignored). Original totals: **1,609** raw selectors, **190** inside assertions, plus console/testLogger items. ## Scope of THIS PR (non-Dashboards) Every non-Dashboards spec is now **100% raw-selector clean** (verified: 0 occurrences). Shards covered: - **GeneralTests, Logs (Core/Builder-Advanced/Features/Regression), Traces (+Reg), Streams (+Reg), Metrics (+Reg), Pipelines, Reports (+Reg), Anomaly, Login/auth, auth-warm, and the unmatched Alerts specs.** - `console.*` → `testLogger` where it was Node-context (kept `console.*` **inside `page.evaluate()`** — that's browser context; `testLogger` doesn't exist there). - `testLogger` import/use added where missing. - **`waitForTimeout` intentionally left as-is** (logged in the audit, not rewritten — per direction). ## Deferred to a follow-up PR The **heavy Dashboards shards** (Variables 693, Config-Settings 255, Tables 127, Charts 81, Core 79, Visualize 65, Settings 52, Panel-DateTime 55, Dashboard-Regression 52 raw selectors) are **not** in this PR — they're the bulk of the work and will be a separate, focused pass. (dashboard-streaming console cleanup is included here.) ## Verification Fixes were applied and executed **shard by shard** against a local env, gated green before commit where the env allowed: | Group | Local result | |-------|--------------| | GeneralTests (edited specs) | 52 passed / 11 skipped / 0 failed | | Logs (shareLink, logsQuickPick, indexquery) | 21 passed | | Traces / Streams / Metrics | 49 passed / 2 skipped / 0 failed | | Pipelines | 56 passed / 17 skipped / 0 failed | | logs-bugs :548, dashboard-streaming | verified not regressed (isolation) | **Alerts create-wizard specs** (Alerts shard, Alerts-Regression, unmatched Alerts) could not be gated locally — the alert-creation wizard's stream popover times out on this env **on unmodified code too** (reproduced on original, test `:363`), so those refactors are 1:1 selector→method mappings, syntax-clean (`node --check`), and rely on CI to gate. Every changed `.js` passes `node --check`. `waitForTimeout` unchanged; no behavior changes intended. ## Stats 51 files changed. Selectors moved into page objects, not deleted.
451 lines
17 KiB
JavaScript
451 lines
17 KiB
JavaScript
/**
|
|
* Share Link End-to-End Tests (Consolidated)
|
|
*
|
|
* Tests for the share link feature on the logs page including:
|
|
* - Share link button visibility and functionality
|
|
* - State preservation across redirects (stream, time range, SQL mode, histogram, org)
|
|
* - Short URL redirect functionality
|
|
* - Edge cases (no stream, multiple clicks, SQL queries)
|
|
*
|
|
* Optimized to avoid duplicate coverage while maintaining comprehensive testing.
|
|
*/
|
|
|
|
const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
|
|
const testLogger = require('../utils/test-logger.js');
|
|
const PageManager = require('../../pages/page-manager.js');
|
|
const logData = require('../../fixtures/log.json');
|
|
|
|
test.describe("Share Link Test Cases", () => {
|
|
// Tests are independent - each has its own beforeEach and fresh page context
|
|
// Running in parallel with 5 workers for faster execution
|
|
let pm;
|
|
const TEST_STREAM = 'e2e_automate';
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
|
|
// Navigate to logs page
|
|
const logsUrl = `${logData.logsUrl}?org_identifier=${process.env["ORGNAME"]}`;
|
|
testLogger.navigation('Navigating to logs page', { url: logsUrl });
|
|
|
|
await page.goto(logsUrl);
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
testLogger.info('Test setup completed');
|
|
});
|
|
|
|
// =====================================================
|
|
// P0 - SMOKE TESTS (Critical Functionality)
|
|
// =====================================================
|
|
|
|
test("P0: Share link button visibility and success notification", {
|
|
tag: ['@shareLink', '@smoke', '@P0']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing share link button visibility and success notification');
|
|
|
|
// Verify share link button is visible
|
|
await pm.logsPage.expectShareLinkButtonVisible();
|
|
|
|
// Select a stream and execute search
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await page.waitForTimeout(2000);
|
|
await pm.logsPage.clickRefresh();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Click share link and verify SUCCESS notification appears
|
|
const success = await pm.logsPage.clickShareLinkAndExpectSuccess();
|
|
expect(success).toBe(true);
|
|
|
|
testLogger.info('Share link visibility and success notification test completed');
|
|
});
|
|
|
|
test("P0: Share link preserves stream and time range after redirect", {
|
|
tag: ['@shareLink', '@statePreservation', '@smoke', '@P0']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing stream and time range preservation via share link redirect');
|
|
|
|
// Step 1: Select stream
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Step 2: Set a specific time range (1 hour)
|
|
await pm.logsPage.setRelativeTimeRange('1-h');
|
|
|
|
// Step 3: Click refresh
|
|
await pm.logsPage.clickRefresh();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Step 4: Capture original state
|
|
const originalState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('Original state captured', { stream: originalState.stream, period: originalState.period });
|
|
|
|
// Step 5: Click share link and get URL
|
|
const sharedUrl = await pm.logsPage.clickShareLinkAndGetUrl();
|
|
testLogger.info('Shared URL captured', { url: sharedUrl });
|
|
|
|
// Step 6: Navigate to shared URL
|
|
await page.goto(sharedUrl);
|
|
await pm.logsPage.waitForRedirectComplete();
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
// Step 7: Capture and verify state
|
|
const redirectedState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('Redirected state', { stream: redirectedState.stream, from: redirectedState.from, to: redirectedState.to });
|
|
|
|
// Verify stream is preserved
|
|
expect(redirectedState.stream).toBe(originalState.stream);
|
|
|
|
// Verify time range: either period=1h OR from/to with ~1 hour duration
|
|
if (redirectedState.period === '1h') {
|
|
expect(redirectedState.period).toBe('1h');
|
|
} else if (redirectedState.from && redirectedState.to) {
|
|
const fromTime = parseInt(redirectedState.from);
|
|
const toTime = parseInt(redirectedState.to);
|
|
const durationHours = (toTime - fromTime) / (1000000 * 60 * 60);
|
|
testLogger.info('Time range duration', { durationHours });
|
|
expect(durationHours).toBeGreaterThan(0.9);
|
|
expect(durationHours).toBeLessThan(1.1);
|
|
} else {
|
|
throw new Error('No time range found in redirected state');
|
|
}
|
|
|
|
testLogger.info('Stream and time range preservation test completed');
|
|
});
|
|
|
|
// =====================================================
|
|
// P1 - FUNCTIONAL TESTS (Feature Validation)
|
|
// =====================================================
|
|
|
|
test("P1: Share link preserves SQL mode toggle state after redirect", {
|
|
tag: ['@shareLink', '@statePreservation', '@functional', '@P1']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing SQL mode preservation via share link redirect');
|
|
|
|
// Step 1: Select stream
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Step 2-4: Deterministically enable SQL mode, set the query, run it (without cancelling
|
|
// the auto-search), and confirm sql_mode committed to the URL — retries once then
|
|
// strict-asserts, so the generated short URL reliably captures sql_mode=true + the query
|
|
// (previously enableSqlModeIfNeeded didn't stick on CI, leaving sql_mode=false).
|
|
await pm.logsPage.setupSqlQueryForShare(`SELECT * FROM "${TEST_STREAM}"`);
|
|
testLogger.info('SQL mode enabled before sharing');
|
|
|
|
// Step 5: Click share link and get URL
|
|
const sharedUrl = await pm.logsPage.clickShareLinkAndGetUrl();
|
|
|
|
// Step 6: Navigate to shared URL
|
|
await page.goto(sharedUrl);
|
|
await pm.logsPage.waitForRedirectComplete();
|
|
|
|
// Step 7: SQL mode is auto-detected from the query editor content, which lazy-loads
|
|
// and re-hydrates AFTER the redirect. Wait for the restored SELECT query to land in
|
|
// the editor before asserting (self-heals with one reload if the lazy editor mounts
|
|
// empty after the short-URL hop) — a fixed 2s wait raced the pre-fill on CI.
|
|
await pm.logsPage.waitForRedirectedQueryEditorContent('SELECT');
|
|
const redirectedState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('Redirected state', redirectedState);
|
|
|
|
// Verify SQL mode is preserved after redirect
|
|
const sqlModeAfterRedirect = await pm.logsPage.isSqlModeEnabled();
|
|
testLogger.info('SQL mode after redirect', { enabled: sqlModeAfterRedirect });
|
|
expect(sqlModeAfterRedirect).toBe(true);
|
|
|
|
testLogger.info('SQL mode preservation test completed');
|
|
});
|
|
|
|
test("P1: Share link preserves histogram toggle state after redirect", {
|
|
tag: ['@shareLink', '@statePreservation', '@functional', '@P1']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing histogram toggle preservation via share link redirect');
|
|
|
|
// Step 1: Select stream
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Step 2: Toggle histogram (now inside utilities hamburger menu)
|
|
await pm.logsPage.toggleHistogram();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Step 3: Click refresh
|
|
await pm.logsPage.clickRefresh();
|
|
|
|
// Step 4: Capture original state. The histogram toggle writes show_histogram to the
|
|
// URL asynchronously, so wait for the param to be present before reading it —
|
|
// otherwise the "original" value itself is captured mid-write on a loaded runner.
|
|
await pm.logsPage.waitForUrlParam('show_histogram');
|
|
const originalState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('Original histogram state', { showHistogram: originalState.showHistogram });
|
|
|
|
// Step 5: Click share link and get URL
|
|
const sharedUrl = await pm.logsPage.clickShareLinkAndGetUrl();
|
|
|
|
// Step 6: Navigate to shared URL
|
|
await page.goto(sharedUrl);
|
|
await pm.logsPage.waitForRedirectComplete();
|
|
|
|
// Step 7: show_histogram is one of the last params the SPA appends when re-hydrating
|
|
// from the short URL. Wait for it to appear (was read as null / a transitional value
|
|
// on CI when captured mid-rewrite) before comparing.
|
|
await pm.logsPage.waitForUrlParam('show_histogram', originalState.showHistogram);
|
|
const redirectedState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('Redirected histogram state', { showHistogram: redirectedState.showHistogram });
|
|
|
|
expect(redirectedState.showHistogram).toBe(originalState.showHistogram);
|
|
|
|
testLogger.info('Histogram toggle preservation test completed');
|
|
});
|
|
|
|
test("P1: Share link preserves complete search state (stream + time + mode + histogram)", {
|
|
tag: ['@shareLink', '@statePreservation', '@functional', '@P1']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing complete search state preservation via share link redirect');
|
|
|
|
// Step 1: Setup complete search state
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Set time range to 30 minutes
|
|
await pm.logsPage.setRelativeTimeRange('30-m');
|
|
|
|
// Step 2: Click refresh
|
|
await pm.logsPage.clickRefresh();
|
|
|
|
// Step 3: Capture complete original state. Wait for the last-written toggle param
|
|
// (show_histogram) so the full search state is in the URL before we snapshot it.
|
|
await pm.logsPage.waitForUrlParam('show_histogram');
|
|
const originalState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('Complete original state captured', originalState);
|
|
|
|
// Step 4: Click share link and get URL
|
|
const sharedUrl = await pm.logsPage.clickShareLinkAndGetUrl();
|
|
testLogger.info('Shared URL', { url: sharedUrl });
|
|
|
|
// Step 5: Navigate to shared URL
|
|
await page.goto(sharedUrl);
|
|
await pm.logsPage.waitForRedirectComplete();
|
|
|
|
// Step 6: Wait for the SPA to finish re-hydrating the toggle params from the short
|
|
// URL (show_histogram is written last) before capturing, then compare.
|
|
await pm.logsPage.waitForUrlParam('show_histogram', originalState.showHistogram);
|
|
const redirectedState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('Redirected state captured', redirectedState);
|
|
|
|
// Step 7: Verify key states are preserved
|
|
const comparison = pm.logsPage.compareStates(originalState, redirectedState, [
|
|
'stream',
|
|
'streamType',
|
|
'showHistogram',
|
|
'quickMode'
|
|
]);
|
|
|
|
expect(comparison.isMatch).toBe(true);
|
|
if (!comparison.isMatch) {
|
|
testLogger.error('State mismatch detected', comparison.differences);
|
|
}
|
|
|
|
// Additionally verify the time range duration is approximately 30 minutes
|
|
if (redirectedState.from && redirectedState.to) {
|
|
const durationMinutes = (parseInt(redirectedState.to) - parseInt(redirectedState.from)) / (1000000 * 60);
|
|
testLogger.info('Time range duration', { durationMinutes });
|
|
expect(durationMinutes).toBeGreaterThan(25);
|
|
expect(durationMinutes).toBeLessThan(35);
|
|
}
|
|
|
|
testLogger.info('Complete state preservation test completed successfully');
|
|
});
|
|
|
|
test("P1: Share link button shows loading state while generating", {
|
|
tag: ['@shareLink', '@functional', '@P1']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing share link loading state');
|
|
|
|
// Select stream and refresh
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await pm.logsPage.clickRefresh();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Get the share button
|
|
const shareButton = pm.logsPage.getShareLinkButtonLocator();
|
|
|
|
// Click and wait for success notification
|
|
await shareButton.click();
|
|
await pm.logsPage.expectShareLinkSuccessNotification();
|
|
|
|
testLogger.info('Loading state test completed');
|
|
});
|
|
|
|
// =====================================================
|
|
// P2 - EDGE CASE TESTS
|
|
// =====================================================
|
|
|
|
test("P2: Share link without stream selected", {
|
|
tag: ['@shareLink', '@edge', '@P2']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing share link without stream selected');
|
|
|
|
// Don't select any stream, just wait for page to load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Share link button should still be visible
|
|
await pm.logsPage.expectShareLinkButtonVisible();
|
|
|
|
// Click share link and verify notification appears (success or error)
|
|
const result = await pm.logsPage.clickShareLinkAndExpectNotification();
|
|
expect(result.appeared).toBe(true);
|
|
testLogger.info('Share link notification received', { text: result.text });
|
|
|
|
testLogger.info('No stream share link test completed');
|
|
});
|
|
|
|
test("P2: Multiple share link clicks work correctly", {
|
|
tag: ['@shareLink', '@edge', '@P2']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing multiple share link clicks');
|
|
|
|
// Select stream and refresh
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await pm.logsPage.clickRefresh();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Click share link multiple times
|
|
for (let i = 0; i < 3; i++) {
|
|
await pm.logsPage.clickShareLinkButton();
|
|
await page.waitForTimeout(2000);
|
|
testLogger.info(`Share link click ${i + 1} completed`);
|
|
}
|
|
|
|
// Last click should still show success
|
|
await pm.logsPage.expectShareLinkSuccessNotification();
|
|
|
|
testLogger.info('Multiple clicks share link test completed');
|
|
});
|
|
|
|
test("P2: Share link with SQL query preserves query content after redirect", {
|
|
tag: ['@shareLink', '@statePreservation', '@edge', '@P2']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing SQL query preservation via share link redirect');
|
|
|
|
// Step 1: Select stream
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Step 2: Enable SQL mode
|
|
await pm.logsPage.enableSqlModeIfNeeded();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Step 3: Enter a SQL query
|
|
const queryEditor = pm.logsPage.getQueryEditorLocator();
|
|
await queryEditor.click();
|
|
await page.keyboard.type(`SELECT * FROM "${TEST_STREAM}" LIMIT 50`);
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Step 4: Click refresh
|
|
await pm.logsPage.clickRefresh();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Step 5: Capture original state
|
|
const originalUrl = await pm.logsPage.getCurrentUrl();
|
|
testLogger.info('Original URL with query', { url: originalUrl });
|
|
|
|
// Verify original URL contains the stream and SQL mode
|
|
expect(originalUrl).toContain(TEST_STREAM);
|
|
expect(originalUrl).toContain('sql_mode=true');
|
|
|
|
// Step 6: Click share link and get URL
|
|
const sharedUrl = await pm.logsPage.clickShareLinkAndGetUrl();
|
|
testLogger.info('Shared URL', { url: sharedUrl });
|
|
|
|
// Step 7: Navigate to shared URL
|
|
await page.goto(sharedUrl);
|
|
await pm.logsPage.waitForRedirectComplete();
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
// Step 8: Verify we're back on logs page with state
|
|
const redirectedUrl = await pm.logsPage.getCurrentUrl();
|
|
expect(redirectedUrl).toContain('logs');
|
|
expect(redirectedUrl).toContain('stream');
|
|
|
|
testLogger.info('SQL query preservation test completed');
|
|
});
|
|
|
|
test("P2: Shared URL redirect, org context, and multiple access consistency", {
|
|
tag: ['@shareLink', '@statePreservation', '@edge', '@P2']
|
|
}, async ({ page }) => {
|
|
|
|
testLogger.info('Testing shared URL redirect, org context, and multiple access consistency');
|
|
|
|
// Step 1: Setup and share
|
|
await pm.logsPage.selectStream(TEST_STREAM);
|
|
await page.waitForTimeout(2000);
|
|
|
|
await pm.logsPage.setRelativeTimeRange('1-h');
|
|
|
|
await pm.logsPage.clickRefresh();
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Capture original org
|
|
const originalState = await pm.logsPage.captureCurrentState();
|
|
const originalOrg = originalState.orgIdentifier;
|
|
|
|
const sharedUrl = await pm.logsPage.clickShareLinkAndGetUrl();
|
|
testLogger.info('Captured shared URL', { url: sharedUrl });
|
|
|
|
// Step 2: Verify it's a short URL
|
|
const isShortUrl = sharedUrl.includes('/short/');
|
|
testLogger.info('URL type', { isShortUrl });
|
|
|
|
// Step 3: Access the URL first time
|
|
await page.goto(sharedUrl);
|
|
await pm.logsPage.waitForRedirectComplete();
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
const firstAccessState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('First access state', firstAccessState);
|
|
|
|
// Verify redirect completed (not on short URL anymore)
|
|
const firstUrl = await pm.logsPage.getCurrentUrl();
|
|
expect(firstUrl).toContain('logs');
|
|
expect(firstUrl).not.toContain('/short/');
|
|
|
|
// Verify org is preserved
|
|
expect(firstAccessState.orgIdentifier).toBe(originalOrg);
|
|
|
|
// Step 4: Navigate away
|
|
await page.goto(`${process.env["ZO_BASE_URL"]}/web/?org_identifier=${process.env["ORGNAME"]}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
// Step 5: Access the URL second time
|
|
await page.goto(sharedUrl);
|
|
await pm.logsPage.waitForRedirectComplete();
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
const secondAccessState = await pm.logsPage.captureCurrentState();
|
|
testLogger.info('Second access state', secondAccessState);
|
|
|
|
// Step 6: Verify states match
|
|
const comparison = pm.logsPage.compareStates(firstAccessState, secondAccessState, [
|
|
'stream',
|
|
'orgIdentifier'
|
|
]);
|
|
|
|
expect(comparison.isMatch).toBe(true);
|
|
|
|
testLogger.info('Shared URL redirect, org context, and multiple access test completed');
|
|
});
|
|
|
|
// Bug #9788 test should be moved to RegressionSet/logs-regression.spec.js or similar
|
|
});
|