mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 00:08:55 +00:00
Co-authored-by: Shrinath-O2 <214651540+Shrinath-O2@users.noreply.github.com>
This commit is contained in:
co-authored by
Shrinath-O2
parent
fad4ddf670
commit
6cdafa5297
@@ -237,6 +237,7 @@
|
||||
"landingPage.spec.js",
|
||||
"datetime-picker-copy-paste.spec.js",
|
||||
"dateTime.spec.js",
|
||||
"dateTimeCustomRange.spec.js",
|
||||
"otableSelectCell.spec.js",
|
||||
"shared-components-form-validation.spec.js",
|
||||
"ai-toolsets-form-validation.spec.js",
|
||||
|
||||
@@ -58,6 +58,16 @@ export class DateTimePickerPage {
|
||||
// Apply button — rendered only when the host sets autoApply=false.
|
||||
this.applyBtn = page.locator('[data-test="date-time-apply-btn"]');
|
||||
|
||||
// Custom relative row — value input + unit select. The product DateTime.vue
|
||||
// renders these controls WITHOUT a data-test (testability-only), so scope
|
||||
// structurally: the custom row is the `.relative-row` that holds the native
|
||||
// number input; the unit select is the single combobox inside that row.
|
||||
this.customRow = page
|
||||
.locator('#date-time-menu .relative-row')
|
||||
.filter({ has: page.locator('input[type="number"]') });
|
||||
this.customValueInput = this.customRow.locator('input[type="number"]');
|
||||
this.customPeriodTrigger = this.customRow.locator('[role="combobox"]');
|
||||
|
||||
// ==================== Expected toast messages ====================
|
||||
// Values of common.dateRangeCopied / dateRangePasted / dateRangePasteError
|
||||
// in web/src/locales/languages/en-US.json.
|
||||
@@ -77,6 +87,17 @@ export class DateTimePickerPage {
|
||||
return this.page.locator(`[data-test="date-time-relative-${suffix}-btn"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit option in the custom period select; unit is one of s/m/h/d/w/M.
|
||||
* The OSelect renders each option (ListboxItem/OSelectItem) with the option
|
||||
* value mirrored onto `data-test-value`, so the option is addressable without
|
||||
* the (absent) parent data-test. Only one select's dropdown is open at a time,
|
||||
* so a bare `data-test-value` match is unambiguous.
|
||||
*/
|
||||
customPeriodOption(unit) {
|
||||
return this.page.locator(`[data-test-value="${unit}"]`);
|
||||
}
|
||||
|
||||
// ==================== Panel open / close ====================
|
||||
|
||||
async openPicker() {
|
||||
@@ -248,6 +269,68 @@ export class DateTimePickerPage {
|
||||
return (await this.triggerBtn.innerText()).trim();
|
||||
}
|
||||
|
||||
// ==================== Custom relative value ====================
|
||||
|
||||
/** Fills the custom value input and blurs so the clamp commits. */
|
||||
async setCustomValue(value) {
|
||||
await expect(this.customValueInput).toBeVisible();
|
||||
await this.customValueInput.fill(String(value));
|
||||
await this.customValueInput.blur();
|
||||
}
|
||||
|
||||
/** Focuses (snapshots lastValidCustomValue), empties, then blurs to restore. */
|
||||
async clearCustomValue() {
|
||||
await expect(this.customValueInput).toBeVisible();
|
||||
await this.customValueInput.fill('');
|
||||
await this.customValueInput.blur();
|
||||
}
|
||||
|
||||
async expectCustomValue(expected) {
|
||||
await expect(this.customValueInput).toHaveValue(String(expected));
|
||||
}
|
||||
|
||||
/** Opens the custom period select and picks a unit (s/m/h/d/w/M). */
|
||||
async selectCustomPeriod(unit) {
|
||||
await expect(this.customPeriodTrigger).toBeVisible();
|
||||
await this.customPeriodTrigger.click();
|
||||
await expect(this.customPeriodOption(unit)).toBeVisible();
|
||||
await this.customPeriodOption(unit).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the period select, asserts which units are offered (present) and
|
||||
* dropped (absent), then selects `unit` — all in one popover open so the
|
||||
* absent checks are never satisfied by a not-yet-open popover. The
|
||||
* `toHaveCount(0)` on dropped units also acts as the deterministic settle
|
||||
* gate while the stream's max_query_range restriction propagates.
|
||||
*/
|
||||
async selectCustomPeriodExpectingUnits(unit, presentUnits, absentUnits) {
|
||||
await expect(this.customPeriodTrigger).toBeVisible();
|
||||
await this.customPeriodTrigger.click();
|
||||
for (const u of presentUnits) {
|
||||
await expect(this.customPeriodOption(u)).toHaveCount(1);
|
||||
}
|
||||
for (const u of absentUnits) {
|
||||
await expect(this.customPeriodOption(u)).toHaveCount(0);
|
||||
}
|
||||
await expect(this.customPeriodOption(unit)).toBeVisible();
|
||||
await this.customPeriodOption(unit).click();
|
||||
}
|
||||
|
||||
async expectPresetDisabled(suffix) {
|
||||
await expect(this.relativePeriodBtn(suffix)).toBeDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the transient pre-load restriction to clear. Before the selected
|
||||
* stream's fields settle, `queryRangeRestrictionInHour` is seeded at 100000,
|
||||
* which stamps a `max` attribute onto the custom input; once it resolves to
|
||||
* -1 (no restriction) the max is removed.
|
||||
*/
|
||||
async waitForNoRestriction() {
|
||||
await expect(this.customValueInput).not.toHaveAttribute('max', /[0-9]+/);
|
||||
}
|
||||
|
||||
// ==================== Manual-apply mode ====================
|
||||
|
||||
async expectApplyButtonVisible() {
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
|
||||
const testLogger = require('../utils/test-logger.js');
|
||||
const PageManager = require('../../pages/page-manager.js');
|
||||
|
||||
/**
|
||||
* E2E coverage for the DateTime picker's Custom relative range validation
|
||||
* (web/src/components/DateTime.vue): the hard minimum of 1 + whole-number
|
||||
* truncation, the per-unit ~10-year upper cap, empty-value restore on blur,
|
||||
* re-capping when the unit changes, and the max_query_range restriction clamp.
|
||||
*
|
||||
* The custom value input/unit select had no data-test; these were added in
|
||||
* DateTime.vue (testability-only) so OInput/OSelect can derive the `-field` /
|
||||
* `-trigger` / `-option` selectors used below.
|
||||
*
|
||||
* Parallel-safety: the no-restriction cases share the read-only `e2e_automate`
|
||||
* stream (restriction reset in beforeEach); the restriction case owns its own
|
||||
* dedicated stream `e2e_datetime_restriction`, so no two tests mutate the same
|
||||
* restriction state.
|
||||
*/
|
||||
|
||||
const NO_RESTRICTION_STREAM = 'e2e_automate';
|
||||
const RESTRICTION_STREAM = 'e2e_datetime_restriction';
|
||||
const ORG_ID = process.env.ORGNAME || 'default';
|
||||
|
||||
test.describe('DateTime Custom Relative Range Validation testcases', () => {
|
||||
let pm;
|
||||
|
||||
test.describe('Custom relative value validation (no restriction)', () => {
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testLogger.testStart(testInfo.title, testInfo.file);
|
||||
await navigateToBase(page);
|
||||
pm = new PageManager(page);
|
||||
|
||||
// Clear any max_query_range a prior run may have leaked onto the shared
|
||||
// stream, then select it so useStreamFields resolves the restriction to -1.
|
||||
await pm.dashboardMaxQueryRange.resetMaxQueryRange(NO_RESTRICTION_STREAM);
|
||||
await pm.logsPage.selectStream(NO_RESTRICTION_STREAM);
|
||||
await pm.dateTimePickerPage.openPicker();
|
||||
// The pre-load seed (queryRangeRestrictionInHour=100000) stamps a max onto
|
||||
// the input until stream fields settle; wait for that to clear.
|
||||
await pm.dateTimePickerPage.waitForNoRestriction();
|
||||
testLogger.info('Logs picker opened on the unrestricted stream');
|
||||
});
|
||||
|
||||
// TC-01
|
||||
test('should clamp the custom value to a minimum of 1 and truncate decimals', {
|
||||
tag: ['@date-time-custom-range', '@all', '@P0']
|
||||
}, async () => {
|
||||
testLogger.info('Verifying minimum-of-1 clamp and decimal truncation');
|
||||
|
||||
await pm.dateTimePickerPage.setCustomValue('0');
|
||||
await pm.dateTimePickerPage.expectCustomValue('1');
|
||||
expect(await pm.dateTimePickerPage.getTriggerLabel()).toContain('Past 1 Minute');
|
||||
|
||||
await pm.dateTimePickerPage.setCustomValue('-5');
|
||||
await pm.dateTimePickerPage.expectCustomValue('1');
|
||||
|
||||
await pm.dateTimePickerPage.setCustomValue('2.7');
|
||||
await pm.dateTimePickerPage.expectCustomValue('2');
|
||||
expect(await pm.dateTimePickerPage.getTriggerLabel()).toContain('Past 2 Minutes');
|
||||
|
||||
testLogger.info('Minimum clamp and truncation verified');
|
||||
});
|
||||
|
||||
// TC-02
|
||||
test('should cap the custom value at the per-unit ten-year maximum', {
|
||||
tag: ['@date-time-custom-range', '@all', '@P0']
|
||||
}, async () => {
|
||||
testLogger.info('Verifying the per-unit upper cap');
|
||||
|
||||
// Months cap at 120.
|
||||
await pm.dateTimePickerPage.selectCustomPeriod('M');
|
||||
await pm.dateTimePickerPage.setCustomValue('5000');
|
||||
await pm.dateTimePickerPage.expectCustomValue('120');
|
||||
expect(await pm.dateTimePickerPage.getTriggerLabel()).toContain('Past 120 Months');
|
||||
|
||||
// Minutes cap at 5,256,000.
|
||||
await pm.dateTimePickerPage.selectCustomPeriod('m');
|
||||
await pm.dateTimePickerPage.setCustomValue('40000000000000000000');
|
||||
await pm.dateTimePickerPage.expectCustomValue('5256000');
|
||||
expect(await pm.dateTimePickerPage.getTriggerLabel()).toContain('Past 5256000 Minutes');
|
||||
|
||||
testLogger.info('Per-unit upper cap verified');
|
||||
});
|
||||
|
||||
// TC-03
|
||||
test('should restore the last valid custom value when the field is emptied on blur', {
|
||||
tag: ['@date-time-custom-range', '@all', '@P0']
|
||||
}, async () => {
|
||||
testLogger.info('Verifying empty value is dropped and restored on blur');
|
||||
|
||||
await pm.dateTimePickerPage.setCustomValue('45');
|
||||
await pm.dateTimePickerPage.expectCustomValue('45');
|
||||
|
||||
// Focus snapshots lastValidCustomValue, empty is never applied, blur restores it.
|
||||
await pm.dateTimePickerPage.clearCustomValue();
|
||||
await pm.dateTimePickerPage.expectCustomValue('45');
|
||||
|
||||
testLogger.info('Empty intermediate state restored to the last valid value');
|
||||
});
|
||||
|
||||
// TC-04
|
||||
test('should re-cap the custom value when the unit changes', {
|
||||
tag: ['@date-time-custom-range', '@all', '@P1']
|
||||
}, async () => {
|
||||
testLogger.info('Verifying the value re-caps when the unit changes');
|
||||
|
||||
// Minutes cap at 5,256,000.
|
||||
await pm.dateTimePickerPage.selectCustomPeriod('m');
|
||||
await pm.dateTimePickerPage.setCustomValue('40000000000000000000');
|
||||
await pm.dateTimePickerPage.expectCustomValue('5256000');
|
||||
|
||||
// Switching to months (cap 120) re-caps the value without re-typing.
|
||||
await pm.dateTimePickerPage.selectCustomPeriod('M');
|
||||
await pm.dateTimePickerPage.expectCustomValue('120');
|
||||
expect(await pm.dateTimePickerPage.getTriggerLabel()).toContain('Past 120 Months');
|
||||
|
||||
testLogger.info('Unit change re-cap verified');
|
||||
});
|
||||
|
||||
// TC-06
|
||||
test('should display weeks as weeks in the trigger label', {
|
||||
tag: ['@date-time-custom-range', '@all', '@P2']
|
||||
}, async () => {
|
||||
testLogger.info('Verifying the week label reads weeks, not a multiplied day count');
|
||||
|
||||
await pm.dateTimePickerPage.selectCustomPeriod('w');
|
||||
await pm.dateTimePickerPage.setCustomValue('1');
|
||||
await pm.dateTimePickerPage.expectCustomValue('1');
|
||||
expect(await pm.dateTimePickerPage.getTriggerLabel()).toContain('Past 1 Week');
|
||||
|
||||
testLogger.info('Week label verified');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Custom relative value under a max query range restriction', () => {
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testLogger.testStart(testInfo.title, testInfo.file);
|
||||
await navigateToBase(page);
|
||||
pm = new PageManager(page);
|
||||
|
||||
// Dedicated stream so no parallel test can wipe the restriction mid-assertion.
|
||||
await pm.ingestionPage.ingestionMultiOrgStream(ORG_ID, RESTRICTION_STREAM);
|
||||
await pm.dashboardMaxQueryRange.waitForStreamReady(RESTRICTION_STREAM);
|
||||
await pm.dashboardMaxQueryRange.setMaxQueryRange(2, RESTRICTION_STREAM);
|
||||
await pm.logsPage.selectStream(RESTRICTION_STREAM);
|
||||
await pm.dateTimePickerPage.openPicker();
|
||||
testLogger.info('Logs picker opened on the restricted stream');
|
||||
});
|
||||
|
||||
// TC-05
|
||||
test('should clamp the custom value and narrow the unit list under a max query range restriction', {
|
||||
tag: ['@date-time-custom-range', '@all', '@P1']
|
||||
}, async () => {
|
||||
testLogger.info('Verifying restriction clamp and narrowed unit list');
|
||||
|
||||
// The restriction settles asynchronously; the narrowed unit list is the gate.
|
||||
await pm.dateTimePickerPage.selectCustomPeriodExpectingUnits(
|
||||
'h',
|
||||
['s', 'm', 'h'],
|
||||
['d', 'w', 'M']
|
||||
);
|
||||
|
||||
// With hours selected, an over-limit value clamps to the 2-hour restriction.
|
||||
await pm.dateTimePickerPage.setCustomValue('10');
|
||||
await pm.dateTimePickerPage.expectCustomValue('2');
|
||||
|
||||
// Preset cells beyond the 2-hour limit render disabled.
|
||||
await pm.dateTimePickerPage.expectPresetDisabled('3-h');
|
||||
await pm.dateTimePickerPage.expectPresetDisabled('6-h');
|
||||
|
||||
testLogger.info('Restriction clamp and narrowed unit list verified');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user