mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 08:09:27 +00:00
- Form with zod validation migration for dashboards , getstarted , createdestination(pipeline) , addregexpattern --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
348 lines
16 KiB
JavaScript
348 lines
16 KiB
JavaScript
// Copyright 2026 OpenObserve Inc.
|
|
//
|
|
// Shared Components — form validation E2E tests
|
|
//
|
|
// Covers:
|
|
// AddToDashboard — empty panel title keeps submit disabled / shows error;
|
|
// valid title enables submit
|
|
// TimeRangeEditor — end before start shows range error; valid range enables apply
|
|
|
|
const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
|
|
const testLogger = require('../utils/test-logger.js');
|
|
const PageManager = require('../../pages/page-manager.js');
|
|
|
|
// ── AddToDashboard form validation ────────────────────────────────────────────
|
|
|
|
test.describe("AddToDashboard form validation", () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
let pm;
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
});
|
|
|
|
test("should keep submit button enabled when panel title is empty", {
|
|
tag: ['@shared-components-form-validation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying submit button stays enabled when panel title is empty (R3)');
|
|
|
|
await pm.sharedComponentsFormValidation.navigateToMetrics();
|
|
|
|
// Run a query first so the panel editor toolbar (and Add to Dashboard btn) appears —
|
|
// same pattern used in metrics.spec.js "Add to Dashboard - Cancel flow" test.
|
|
await pm.metricsPage.enterMetricsQuery('up');
|
|
await pm.metricsPage.clickApplyButton();
|
|
await pm.metricsPage.waitForMetricsResults();
|
|
|
|
await pm.metricsPage.openAddToDashboardDialog();
|
|
await pm.sharedComponentsFormValidation.waitForAddToDashboardDialog();
|
|
|
|
testLogger.info('AddToDashboard dialog opened');
|
|
|
|
// Under the OForm foundation the Add button is ALWAYS enabled — the Zod
|
|
// schema gates the save on submit, not a disabled button. So an empty
|
|
// panel title leaves the button enabled (clicking it reveals the error).
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getAddToDashboardSubmitBtnLocator()
|
|
).toBeEnabled();
|
|
|
|
testLogger.info('Submit button correctly stays enabled for empty panel title');
|
|
});
|
|
|
|
test("should show validation error when panel title is submitted empty via form", {
|
|
tag: ['@shared-components-form-validation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying panel title error message when submitted empty');
|
|
|
|
await pm.sharedComponentsFormValidation.navigateToMetrics();
|
|
await pm.metricsPage.enterMetricsQuery('up');
|
|
await pm.metricsPage.clickApplyButton();
|
|
await pm.metricsPage.waitForMetricsResults();
|
|
await pm.metricsPage.openAddToDashboardDialog();
|
|
await pm.sharedComponentsFormValidation.waitForAddToDashboardDialog();
|
|
|
|
// OForm validates on submit — clicking the (always-enabled) Add button
|
|
// with an empty title runs the schema and reveals the required error.
|
|
await pm.sharedComponentsFormValidation.clickAddToDashboardSubmit();
|
|
|
|
// Schema: panelTitle z.string().trim().min(1, "Panel Title required")
|
|
const errorLocator = pm.sharedComponentsFormValidation.getPanelTitleErrorLocator();
|
|
await errorLocator.waitFor({ state: 'visible' });
|
|
await expect(errorLocator).toBeVisible();
|
|
await expect(errorLocator).toContainText('Panel Title required');
|
|
|
|
// The button stays enabled (the schema gates the save, not a disabled button).
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getAddToDashboardSubmitBtnLocator()
|
|
).toBeEnabled();
|
|
|
|
testLogger.info('Panel title error message correctly shown');
|
|
});
|
|
|
|
test("should enable submit button when valid panel title is entered", {
|
|
tag: ['@shared-components-form-validation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying submit button is enabled when valid panel title is provided');
|
|
|
|
await pm.sharedComponentsFormValidation.navigateToMetrics();
|
|
await pm.metricsPage.enterMetricsQuery('up');
|
|
await pm.metricsPage.clickApplyButton();
|
|
await pm.metricsPage.waitForMetricsResults();
|
|
await pm.metricsPage.openAddToDashboardDialog();
|
|
await pm.sharedComponentsFormValidation.waitForAddToDashboardDialog();
|
|
|
|
await pm.sharedComponentsFormValidation.fillPanelTitle('test_fv_alerts_001 Panel');
|
|
|
|
// With a non-empty, non-whitespace title the primary button must be enabled
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getAddToDashboardSubmitBtnLocator()
|
|
).toBeEnabled();
|
|
|
|
testLogger.info('Submit button enabled with valid panel title');
|
|
});
|
|
});
|
|
|
|
// ── TimeRangeEditor form validation ──────────────────────────────────────────
|
|
|
|
test.describe("TimeRangeEditor form validation", () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
let pm;
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
});
|
|
|
|
test("should show error when custom end time is before start time", {
|
|
tag: ['@shared-components-form-validation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying end-before-start error in TimeRangeEditor');
|
|
|
|
// Open the TimeRangeEditor dialog from the Correlation page or any
|
|
// surface that renders the component. We use the correlation settings
|
|
// page which hosts the component via the correlation logs toolbar.
|
|
await pm.correlationSettingsPage.navigateToCorrelationSettings();
|
|
const dialogOpened = await pm.correlationSettingsPage.openTimeRangeEditor();
|
|
test.skip(!dialogOpened, 'TimeRangeEditor trigger not available in this environment');
|
|
await pm.sharedComponentsFormValidation.waitForTimeRangeEditorDialog();
|
|
|
|
testLogger.info('TimeRangeEditor dialog opened');
|
|
|
|
// Switch to custom mode
|
|
await pm.sharedComponentsFormValidation.selectCustomWindow();
|
|
|
|
// Provide a start time AFTER the end time
|
|
// Start: 2025-06-10T12:00, End: 2025-06-10T10:00 (end before start)
|
|
await pm.sharedComponentsFormValidation.fillCustomStartTime('2025-06-10T12:00');
|
|
await pm.sharedComponentsFormValidation.fillCustomEndTime('2025-06-10T10:00');
|
|
|
|
// validateEndTime returns t('correlation.logs.timeRange.endAfterStart') when
|
|
// endMicros <= pendingStartTime
|
|
const endErrorLocator = pm.sharedComponentsFormValidation.getCustomEndErrorLocator();
|
|
await endErrorLocator.waitFor({ state: 'visible' });
|
|
await expect(endErrorLocator).toBeVisible();
|
|
await expect(endErrorLocator).toContainText('End time must be after start time');
|
|
|
|
// Apply button must be disabled (isValid = false)
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getTimeRangeApplyBtnLocator()
|
|
).toBeDisabled();
|
|
|
|
testLogger.info('End-before-start error correctly shown and Apply button disabled');
|
|
});
|
|
|
|
test("should show error when custom start time is after end time", {
|
|
tag: ['@shared-components-form-validation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying start-after-end error in TimeRangeEditor');
|
|
|
|
await pm.correlationSettingsPage.navigateToCorrelationSettings();
|
|
const dialogOpened = await pm.correlationSettingsPage.openTimeRangeEditor();
|
|
test.skip(!dialogOpened, 'TimeRangeEditor trigger not available in this environment');
|
|
await pm.sharedComponentsFormValidation.waitForTimeRangeEditorDialog();
|
|
|
|
await pm.sharedComponentsFormValidation.selectCustomWindow();
|
|
|
|
// First set a valid end time, then set start after it
|
|
await pm.sharedComponentsFormValidation.fillCustomEndTime('2025-06-10T10:00');
|
|
await pm.sharedComponentsFormValidation.fillCustomStartTime('2025-06-10T12:00');
|
|
|
|
// validateStartTime returns t('correlation.logs.timeRange.startBeforeEnd') when
|
|
// startMicros >= pendingEndTime
|
|
const startErrorLocator = pm.sharedComponentsFormValidation.getCustomStartErrorLocator();
|
|
await startErrorLocator.waitFor({ state: 'visible' });
|
|
await expect(startErrorLocator).toBeVisible();
|
|
await expect(startErrorLocator).toContainText('Start time must be before end time');
|
|
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getTimeRangeApplyBtnLocator()
|
|
).toBeDisabled();
|
|
|
|
testLogger.info('Start-after-end error correctly shown and Apply button disabled');
|
|
});
|
|
|
|
test("should enable apply button when valid custom time range is provided", {
|
|
tag: ['@shared-components-form-validation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying Apply button enabled for valid custom time range');
|
|
|
|
await pm.correlationSettingsPage.navigateToCorrelationSettings();
|
|
const dialogOpened = await pm.correlationSettingsPage.openTimeRangeEditor();
|
|
test.skip(!dialogOpened, 'TimeRangeEditor trigger not available in this environment');
|
|
await pm.sharedComponentsFormValidation.waitForTimeRangeEditorDialog();
|
|
|
|
await pm.sharedComponentsFormValidation.selectCustomWindow();
|
|
|
|
// Valid range: start strictly before end
|
|
await pm.sharedComponentsFormValidation.fillCustomStartTime('2025-06-10T08:00');
|
|
await pm.sharedComponentsFormValidation.fillCustomEndTime('2025-06-10T10:00');
|
|
|
|
// No errors
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getCustomStartErrorLocator()
|
|
).not.toBeVisible();
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getCustomEndErrorLocator()
|
|
).not.toBeVisible();
|
|
|
|
// Apply button must be enabled
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getTimeRangeApplyBtnLocator()
|
|
).toBeEnabled();
|
|
|
|
testLogger.info('Apply button enabled with valid time range');
|
|
});
|
|
|
|
test("should enable apply button when preset window is selected", {
|
|
tag: ['@shared-components-form-validation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying Apply button enabled for preset window selection');
|
|
|
|
await pm.correlationSettingsPage.navigateToCorrelationSettings();
|
|
const dialogOpened = await pm.correlationSettingsPage.openTimeRangeEditor();
|
|
test.skip(!dialogOpened, 'TimeRangeEditor trigger not available in this environment');
|
|
await pm.sharedComponentsFormValidation.waitForTimeRangeEditorDialog();
|
|
|
|
// Select the 5-minute preset (default selection)
|
|
await pm.sharedComponentsFormValidation.selectPresetWindow('5min');
|
|
|
|
// isValid = pendingStartTime < pendingEndTime — preset sets symmetric range
|
|
await expect(
|
|
pm.sharedComponentsFormValidation.getTimeRangeApplyBtnLocator()
|
|
).toBeEnabled();
|
|
|
|
testLogger.info('Apply button enabled with preset window');
|
|
});
|
|
});
|
|
|
|
// ── TimeRangeEditor — Logs Correlation tab ────────────────────────────────────
|
|
|
|
test.describe("TimeRangeEditor Logs Correlation form validation", () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
let pm;
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
});
|
|
|
|
test("should show custom time inputs when custom window is selected", {
|
|
tag: ['@domainFormValidation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying custom start/end inputs appear when window-custom is selected');
|
|
|
|
// Open TimeRangeEditor from Logs > Correlation tab
|
|
const available = await pm.sharedComponentsFormValidation.navigateToLogsCorrelation()
|
|
.then(() => pm.sharedComponentsFormValidation.openTimeRangeEditorFromCorrelation())
|
|
.then(() => true).catch(() => false);
|
|
test.skip(!available, 'Logs Correlation TimeRangeEditor not available in this environment');
|
|
await pm.sharedComponentsFormValidation.waitForTimeRangeEditorDialog();
|
|
|
|
testLogger.info('TimeRangeEditor dialog opened from Logs Correlation tab');
|
|
|
|
// Click the custom preset button
|
|
await pm.sharedComponentsFormValidation.selectCustomWindow();
|
|
|
|
// Custom start and end inputs must now be visible
|
|
const startInput = pm.sharedComponentsFormValidation.getCustomStartInputLocator();
|
|
const endInput = pm.sharedComponentsFormValidation.getCustomEndInputLocator();
|
|
|
|
await startInput.waitFor({ state: 'visible' });
|
|
await expect(startInput).toBeVisible();
|
|
await expect(endInput).toBeVisible();
|
|
|
|
testLogger.info('Custom start/end inputs are visible after selecting window-custom');
|
|
});
|
|
|
|
test("should show error or disable save when end time is before start time in custom range", {
|
|
tag: ['@domainFormValidation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying error/disabled state when end time is before start time');
|
|
|
|
const available = await pm.sharedComponentsFormValidation.navigateToLogsCorrelation()
|
|
.then(() => pm.sharedComponentsFormValidation.openTimeRangeEditorFromCorrelation())
|
|
.then(() => true).catch(() => false);
|
|
test.skip(!available, 'Logs Correlation TimeRangeEditor not available in this environment');
|
|
await pm.sharedComponentsFormValidation.waitForTimeRangeEditorDialog();
|
|
|
|
// Switch to custom mode
|
|
await pm.sharedComponentsFormValidation.selectCustomWindow();
|
|
|
|
// Fill start time AFTER end time: start 2025-06-10T12:00, end 2025-06-10T10:00
|
|
await pm.sharedComponentsFormValidation.fillCustomStartTime('2025-06-10T12:00');
|
|
await pm.sharedComponentsFormValidation.fillCustomEndTime('2025-06-10T10:00');
|
|
|
|
// Either an end-time error is shown OR the Apply button is disabled
|
|
const endError = pm.sharedComponentsFormValidation.getCustomEndErrorLocator();
|
|
const applyBtn = pm.sharedComponentsFormValidation.getTimeRangeApplyBtnLocator();
|
|
|
|
const errorVisible = await endError.isVisible().catch(() => false);
|
|
const applyDisabled = await applyBtn.isDisabled().catch(() => false);
|
|
|
|
expect(errorVisible || applyDisabled).toBe(true);
|
|
|
|
testLogger.info('Error shown or Apply button disabled for end-before-start custom range');
|
|
});
|
|
|
|
test("should select preset window successfully", {
|
|
tag: ['@domainFormValidation', '@P0', '@smoke']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Verifying 5min preset window can be selected and is reflected in UI');
|
|
|
|
const available = await pm.sharedComponentsFormValidation.navigateToLogsCorrelation()
|
|
.then(() => pm.sharedComponentsFormValidation.openTimeRangeEditorFromCorrelation())
|
|
.then(() => true).catch(() => false);
|
|
test.skip(!available, 'Logs Correlation TimeRangeEditor not available in this environment');
|
|
await pm.sharedComponentsFormValidation.waitForTimeRangeEditorDialog();
|
|
|
|
const window5min = pm.sharedComponentsFormValidation.getWindow5minLocator();
|
|
await window5min.waitFor({ state: 'visible' });
|
|
|
|
// Click the 5min preset
|
|
await pm.sharedComponentsFormValidation.selectPresetWindow('5min');
|
|
|
|
// Verify selection state: element should have aria-pressed=true, aria-selected=true,
|
|
// or a class indicating active/selected state
|
|
const isPressed = await window5min.getAttribute('aria-pressed');
|
|
const isSelected = await window5min.getAttribute('aria-selected');
|
|
const classAttr = await window5min.getAttribute('class');
|
|
|
|
const isActive =
|
|
isPressed === 'true' ||
|
|
isSelected === 'true' ||
|
|
(classAttr !== null && /active|selected|checked/i.test(classAttr));
|
|
|
|
// Additionally, the Apply button should be enabled (preset is always valid)
|
|
const applyBtn = pm.sharedComponentsFormValidation.getTimeRangeApplyBtnLocator();
|
|
|
|
// At minimum the Apply button must be available (preset range is valid)
|
|
await expect(applyBtn).toBeEnabled();
|
|
|
|
testLogger.info('Preset window-5min selected; Apply button is enabled');
|
|
});
|
|
});
|