Files
openobserve/tests/ui-testing/playwright-tests/GeneralTests/shared-components-form-validation.spec.js
T
ktx-mihirandgithub-actions[bot] 8b3bd75783 fix: ux revamp form validation migration v1 (#12747)
- Form with zod validation migration for dashboards , getstarted ,
createdestination(pipeline) , addregexpattern

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-19 11:30:27 +00:00

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');
});
});