From 46fb33c7286c1e0f9ea034d95d770a93f930eed5 Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Fri, 20 Jun 2025 16:22:08 +0530 Subject: [PATCH 1/8] Added automated tests with cypress for Schedule form under Application-Settings --- .../Application-Settings/schedule.cy.js | 341 ++++++++++++++++++ cypress/support/e2e.js | 3 +- 2 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js diff --git a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js new file mode 100644 index 00000000000..ac5f0fd60ec --- /dev/null +++ b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js @@ -0,0 +1,341 @@ +/* eslint-disable no-undef */ + +function selectConfigMenu(configuration = 'Add a new Schedule') { + cy.get('#miq_schedule_vmdb_choice').click(); + cy.get(`ul[aria-label="Configuration"] [title="${configuration}"]`).click(); +} + +function addSchedule() { + selectConfigMenu(); + // Checks if Save button is disabled initially + cy.contains( + '#main-content .bx--btn-set button[type="submit"]', + 'Save' + ).should('be.disabled'); + // Adding data + cy.get('input#name').type('Test name'); + cy.get('input#description').type('Test description'); + cy.get('input[type="checkbox"]#enabled').check({ force: true }); + cy.get('select#action_typ').select('VM Analysis'); + cy.get('select#filter_typ').select('A single VM'); + cy.get('select#timer_typ').select('Hourly'); + cy.get('select#timer_value').select('1 Hour'); + cy.get('input[role="combobox"]#time_zone').click(); + cy.contains('[role="option"]', '(GMT-10:00) Hawaii') + .should('be.visible') + .click(); + cy.get('input#start_date').type('06/30/2025'); + cy.get('input#start_time').type('11:23'); + // Checks if Save button is enabled once all required fields are filled + cy.contains('#main-content .bx--btn-set button[type="submit"]', 'Save') + .should('be.enabled') + .click(); +} + +function deleteSchedule(scheduleName = 'Test name') { + // Selecting the schedule + cy.contains('li.list-group-item', scheduleName).click(); + cy.on('window:confirm', (text) => { + expect(text).to.eq( + 'Warning: This Schedule and ALL of its components will be permanently removed!' + ); + return true; + }); + selectConfigMenu('Delete this Schedule from the Database'); + cy.get('#main_div #flash_msg_div .alert-success').contains( + `Schedule "${scheduleName}": Delete successful` + ); +} + +function invokeCleanupDeletion() { + // Iterate and clean up any leftover schedules created during the test + cy.get('li.list-group-item').each(($el) => { + const text = $el?.text()?.trim(); + if (text === 'Test name') { + deleteSchedule(); + return false; + } + if (text === 'Dummy name') { + deleteSchedule('Dummy name'); + return false; + } + return true; + }); +} + +function verifyFilterTypeDropdownExists() { + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); +} + +function verifyTimerDropdownExists() { + cy.get('label[for="timer_value"]').should('exist'); + cy.get('select#timer_value').should('exist'); +} + +describe('Automate Schedule form operations: Settings > Application Settings > Settings > Schedules > Configuration > Add a new schedule', () => { + beforeEach(() => { + cy.login(); + cy.menu(settingsMenuOption, appSettingsMenuOption); + cy.intercept('POST', '/ops/tree_select?id=xx-msc&text=Schedules').as( + 'getSchedules' + ); + cy.get('[title="Schedules"]').click(); + cy.wait('@getSchedules'); + }); + + it('Validate visibility of elements based on dropdown selections', () => { + selectConfigMenu(); + + /* ===== Selecting any option other than "Automation Tasks" from "Action" dropdown does not hide the Filter dropdown ===== */ + + cy.get('select#action_typ').select('VM Analysis'); + cy.get('select#action_typ').should('have.value', 'vm'); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + cy.get('select#action_typ').select('Template Analysis'); + cy.get('select#action_typ').should('have.value', 'miq_template'); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + cy.get('select#action_typ').select('Host Analysis'); + cy.get('select#action_typ').should('have.value', 'host'); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + cy.get('select#action_typ').select('Container Image Analysis'); + cy.get('select#action_typ').should('have.value', 'container_image'); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + cy.get('select#action_typ').select('Cluster Analysis'); + cy.get('select#action_typ').should('have.value', 'emscluster'); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + cy.get('select#action_typ').select('Datastore Analysis'); + cy.get('select#action_typ').should('have.value', 'storage'); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + cy.get('select#action_typ').select('VM Compliance Check'); + cy.get('select#action_typ').should('have.value', 'vm_check_compliance'); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + cy.get('select#action_typ').select('Host Compliance Check'); + cy.get('select#action_typ').should('have.value', 'host_check_compliance'); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + cy.get('select#action_typ').select('Container Image Compliance Check'); + cy.get('select#action_typ').should( + 'have.value', + 'container_image_check_compliance' + ); + // Checking for Filter type dropdown + cy.get('label[for="filter_typ"]').should('exist'); + cy.get('select#filter_typ').should('exist'); + + /* ===== Selecting "Automation Tasks" option from "Action" dropdown shows Zone, Object details & Object fields ===== */ + + cy.get('select#action_typ').select('Automation Tasks'); + cy.get('select#action_typ').should('have.value', 'automation_request'); + + // Checking for Zone dropdown + cy.get('label[for="zone_id"]').should('exist'); + cy.get('select#zone_id').should('exist'); + + // Checking for Object Details + cy.get('h3[name="object_details"]').should('exist'); + // Checking for System/Process dropdown + cy.get('label[for="instance_name"]').should('exist'); + cy.get('select#instance_name').should('exist'); + // Checking for Messsage textfield + cy.get('label[for="message"]').should('exist'); + cy.get('input#message').should('exist'); + // Checking for Request textfield + cy.get('label[for="request"]').should('exist'); + cy.get('input#request').should('exist'); + + // Checking for Object + cy.get('h3[name="object_attributes"]').should('exist'); + // Checking for Type Combobox + cy.get('label[for="target_class"]').should('exist'); + cy.get('input[role="combobox"]#target_class').should('exist'); + // Checking for Object Combobox + cy.get('label[for="target_id"]').should('exist'); + cy.get('input[role="combobox"]#target_id').should('exist'); + + // Checking for Attribute/Value pairs + cy.contains('h3', 'Attribute/Value Pairs').should('exist'); + // Checking for 5 attribute-value pairs text fields + cy.get('input#attribute_1').should('exist'); + cy.get('input#value_1').should('exist'); + cy.get('input#attribute_2').should('exist'); + cy.get('input#value_2').should('exist'); + cy.get('input#attribute_3').should('exist'); + cy.get('input#value_3').should('exist'); + cy.get('input#attribute_4').should('exist'); + cy.get('input#value_4').should('exist'); + cy.get('input#attribute_5').should('exist'); + cy.get('input#value_5').should('exist'); + + /* ===== Selecting "Once" option from "Run" dropdown does not show the "Every" dropdown ===== */ + + cy.get('select#timer_typ').select('Once'); + // Checking whether the Every dropdown is hidden + cy.get('input#timer_value').should('not.exist'); + + /* ===== Selecting any other option other than "Once" from "Run" dropdown shows the "Every" dropdown ===== */ + + cy.get('select#timer_typ').select('Hours'); + // Checking whether the "Every" dropdown exist + cy.get('label[for="timer_value"]').should('exist'); + cy.get('select#timer_value').should('exist'); + + cy.get('select#timer_typ').select('Days'); + // Checking whether the "Every" dropdown exist + cy.get('label[for="timer_value"]').should('exist'); + cy.get('select#timer_value').should('exist'); + + cy.get('select#timer_typ').select('Weeks'); + // Checking whether the "Every" dropdown exist + cy.get('label[for="timer_value"]').should('exist'); + cy.get('select#timer_value').should('exist'); + + cy.get('select#timer_typ').select('Months'); + // Checking whether the "Every" dropdown exist + cy.get('label[for="timer_value"]').should('exist'); + cy.get('select#timer_value').should('exist'); + }); + + it('Checking whether Cancel button works on the Add form', () => { + selectConfigMenu(); + cy.contains('#main-content .bx--btn-set button[type="button"]', 'Cancel') + .should('be.enabled') + .click(); + cy.get('#main_div #flash_msg_div .alert-success').contains( + 'Add was cancelled by the user' + ); + }); + + it('Checking whether add, edit & delete schedule works', () => { + /* ===== Adding a schedule ===== */ + addSchedule(); + cy.get('#main_div #flash_msg_div .alert-success').contains( + 'Schedule "Test name" was saved' + ); + + /* ===== Editing a schedule ===== */ + // Selecting the created schedule + cy.contains('li.list-group-item', 'Test name').click(); + selectConfigMenu('Edit this Schedule'); + // Editing name and description + cy.get('input#name').clear().type('Dummy name'); + cy.get('input#description').clear().type('Dummy description'); + // Confirms Save button is enabled after making edits + cy.contains('#main-content .bx--btn-set button[type="submit"]', 'Save') + .should('be.enabled') + .click(); + cy.get('#main_div #flash_msg_div .alert-success').contains( + 'Schedule "Dummy name" was saved' + ); + + /* ===== Delete is already handled from afterEach hook ===== */ + }); + + it('Checking whether Cancel & Reset buttons work fine in the Edit form', () => { + /* ===== Adding a schedule ===== */ + addSchedule(); + + /* ===== Checking whether Cancel button works ===== */ + // Selecting the created schedule + cy.contains('li.list-group-item', 'Test name').click(); + selectConfigMenu('Edit this Schedule'); + cy.contains('#main-content .bx--btn-set button[type="button"]', 'Cancel') + .should('be.enabled') + .click(); + cy.get('#main_div #flash_msg_div .alert-success').contains( + 'Edit of "Test name" was cancelled by the user' + ); + + /* ===== Checking whether Reset button works ===== */ + // Selecting the created schedule + cy.contains('li.list-group-item', 'Test name').click(); + selectConfigMenu('Edit this Schedule'); + // Editing description and start date + cy.get('input#description').clear().type('Dummy description'); + cy.get('input#start_date').clear().type('07/21/2025'); + cy.contains('#main-content .bx--btn-set button[type="button"]', 'Reset') + .should('be.enabled') + .click(); + cy.get('#main_div #flash_msg_div .alert-warning').contains( + 'All changes have been reset' + ); + // Confirming the edited fields contain the old values after resetting + cy.get('input#description').should('have.value', 'Test description'); + cy.get('input#start_date').should('have.value', '06/30/2025'); + + // Selecting Schedules menu item to bypass a bug, can be removed once #9505 is merged + cy.get('[title="Schedules"]').click(); + }); + + it('Checking whether creating a duplicate record is restricted', () => { + /* ===== Adding schedule ===== */ + addSchedule(); + + /* ===== Trying to add the same schedule again ===== */ + addSchedule(); + cy.get('#main_div #flash_msg_div .alert-danger').contains( + 'Error when adding a new schedule: Validation failed: MiqSchedule: Name has already been taken' + ); + }); + + it('Checking whether Disabling, Enabling & Queueing up the schedule works', () => { + /* ===== Adding a schedule ===== */ + addSchedule(); + // Selecting the created schedule + cy.contains('li.list-group-item', 'Test name').click(); + + /* ===== Disabling the schedule ===== */ + selectConfigMenu('Disable this Schedule'); + cy.get('#main_div #flash_msg_div .alert-info').contains( + 'The selected Schedules were disabled' + ); + + /* ===== Enabling the schedule ===== */ + selectConfigMenu('Enable this Schedule'); + cy.get('#main_div #flash_msg_div .alert-info').contains( + 'The selected Schedules were enabled' + ); + + /* ===== Queueing-up the schedule ===== */ + selectConfigMenu('Queue up this Schedule to run now'); + cy.get('#main_div #flash_msg_div .alert-success').contains( + 'The selected Schedule has been queued to run' + ); + }); + + afterEach(() => { + cy?.url()?.then((url) => { + // Ensures navigation to Settings -> Application-Settings in the UI + if (url?.includes('/ops/explorer')) { + invokeCleanupDeletion(); + } else { + // Navigate to Settings -> Application-Settings before looking out for Schedules created during test + cy.menu('Settings', 'Application Settings'); + invokeCleanupDeletion(); + } + }); + }); +}); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index b3daa059bc8..54cdf15b634 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -59,7 +59,8 @@ import './assertions/expect_text.js' // Network and aborted errors are exlusive to firefox when cypress navigates to a new page before the api calls for the last page are fullly loaded Cypress.on('uncaught:exception', (err, runnable) => { console.log(err.message); - if (err.message.includes(`Cannot read properties of undefined (reading 'received')`) || // Error handler for Chrome + if (err.message.includes(`Cannot read properties of undefined (reading 'received')`) || // Error handler for Chrome + err.message.includes(`Cannot read properties of undefined (reading '0')`) || // Error handler for Chrome err.message.includes('subscription is undefined') || // Error handler for Firefox err.message.includes('NetworkError when attempting to fetch resource.') || // Error handler for Firefox err.message.includes('The operation was aborted.')) // Error handler for Firefox From 32e94ed6e6477b8a675c4b6c96e473d5a8cba4bb Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Mon, 30 Jun 2025 15:02:59 +0530 Subject: [PATCH 2/8] Introduced cypress commands to assert flash-alerts --- .../Application-Settings/schedule.cy.js | 56 ++++++------------- cypress/support/assertions/expect_alerts.js | 49 ++++++++++++++++ cypress/support/e2e.js | 1 + 3 files changed, 66 insertions(+), 40 deletions(-) create mode 100644 cypress/support/assertions/expect_alerts.js diff --git a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js index ac5f0fd60ec..5e22917a7f0 100644 --- a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js +++ b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js @@ -35,16 +35,10 @@ function addSchedule() { function deleteSchedule(scheduleName = 'Test name') { // Selecting the schedule cy.contains('li.list-group-item', scheduleName).click(); - cy.on('window:confirm', (text) => { - expect(text).to.eq( - 'Warning: This Schedule and ALL of its components will be permanently removed!' - ); - return true; - }); - selectConfigMenu('Delete this Schedule from the Database'); - cy.get('#main_div #flash_msg_div .alert-success').contains( - `Schedule "${scheduleName}": Delete successful` - ); + // Listening for the browser confirm alert and confirming + cy.listen_for_browser_confirm_alert(); + selectConfigMenu(deleteScheduleConfigOption); + cy.expect_flash('success'); } function invokeCleanupDeletion() { @@ -224,17 +218,13 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.contains('#main-content .bx--btn-set button[type="button"]', 'Cancel') .should('be.enabled') .click(); - cy.get('#main_div #flash_msg_div .alert-success').contains( - 'Add was cancelled by the user' - ); + cy.expect_flash('success'); }); it('Checking whether add, edit & delete schedule works', () => { /* ===== Adding a schedule ===== */ addSchedule(); - cy.get('#main_div #flash_msg_div .alert-success').contains( - 'Schedule "Test name" was saved' - ); + cy.expect_flash('success'); /* ===== Editing a schedule ===== */ // Selecting the created schedule @@ -247,9 +237,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.contains('#main-content .bx--btn-set button[type="submit"]', 'Save') .should('be.enabled') .click(); - cy.get('#main_div #flash_msg_div .alert-success').contains( - 'Schedule "Dummy name" was saved' - ); + cy.expect_flash('success'); /* ===== Delete is already handled from afterEach hook ===== */ }); @@ -265,9 +253,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.contains('#main-content .bx--btn-set button[type="button"]', 'Cancel') .should('be.enabled') .click(); - cy.get('#main_div #flash_msg_div .alert-success').contains( - 'Edit of "Test name" was cancelled by the user' - ); + cy.expect_flash('success'); /* ===== Checking whether Reset button works ===== */ // Selecting the created schedule @@ -279,9 +265,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.contains('#main-content .bx--btn-set button[type="button"]', 'Reset') .should('be.enabled') .click(); - cy.get('#main_div #flash_msg_div .alert-warning').contains( - 'All changes have been reset' - ); + cy.expect_flash('warning'); // Confirming the edited fields contain the old values after resetting cy.get('input#description').should('have.value', 'Test description'); cy.get('input#start_date').should('have.value', '06/30/2025'); @@ -296,9 +280,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S /* ===== Trying to add the same schedule again ===== */ addSchedule(); - cy.get('#main_div #flash_msg_div .alert-danger').contains( - 'Error when adding a new schedule: Validation failed: MiqSchedule: Name has already been taken' - ); + cy.expect_flash('error'); }); it('Checking whether Disabling, Enabling & Queueing up the schedule works', () => { @@ -308,22 +290,16 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.contains('li.list-group-item', 'Test name').click(); /* ===== Disabling the schedule ===== */ - selectConfigMenu('Disable this Schedule'); - cy.get('#main_div #flash_msg_div .alert-info').contains( - 'The selected Schedules were disabled' - ); + selectConfigMenu(disableScheduleConfigOption); + cy.expect_flash('info'); /* ===== Enabling the schedule ===== */ - selectConfigMenu('Enable this Schedule'); - cy.get('#main_div #flash_msg_div .alert-info').contains( - 'The selected Schedules were enabled' - ); + selectConfigMenu(enableScheduleConfigOption); + cy.expect_flash('info'); /* ===== Queueing-up the schedule ===== */ - selectConfigMenu('Queue up this Schedule to run now'); - cy.get('#main_div #flash_msg_div .alert-success').contains( - 'The selected Schedule has been queued to run' - ); + selectConfigMenu(queueScheduleConfigOption); + cy.expect_flash('success'); }); afterEach(() => { diff --git a/cypress/support/assertions/expect_alerts.js b/cypress/support/assertions/expect_alerts.js new file mode 100644 index 00000000000..1c6c2ba3938 --- /dev/null +++ b/cypress/support/assertions/expect_alerts.js @@ -0,0 +1,49 @@ +/* eslint-disable no-undef */ + +/** + * Custom Cypress command to validate flash messages. + * @param {string} alertType - Type of alert (success, warning, error, info). + * @param {string} [containsText] - Optional text that the alert should contain. + * @returns {Cypress.Chainable} - The alert element if found, or an assertion failure. + */ +Cypress.Commands.add('expect_flash', (alertType = 'success', containsText) => { + let alertClassName; + switch (alertType) { + case 'warning': + alertClassName = 'warning'; + break; + case 'error': + alertClassName = 'danger'; + break; + case 'info': + alertClassName = 'info'; + break; + default: + alertClassName = 'success'; + break; + } + const alert = cy + .get(`#main_div #flash_msg_div .alert-${alertClassName}`) + .should('be.visible'); + + if (containsText) { + return alert.should(($el) => { + const actualText = $el.text().toLowerCase(); + expect(actualText).to.include(containsText.toLowerCase()); + }); + } + + return alert; +}); + +Cypress.Commands.add( + 'listen_for_browser_confirm_alert', + (containsText, proceed = true) => { + cy.on('window:confirm', (actualText) => { + if (containsText) { + expect(actualText.toLowerCase()).to.include(containsText.toLowerCase()); + } + return proceed; // true = OK, false = Cancel + }); + } +); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 54cdf15b634..1449a55f3ad 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -53,6 +53,7 @@ import './assertions/expect_rates_table.js'; import './assertions/expect_search_box.js' import './assertions/expect_title.js' import './assertions/expect_text.js' +import './assertions/expect_alerts.js'; // This is needed to prevent Cypress tests from failing due to uncaught errors: // Undefined errors are occuring on every initial page load of Manage IQ From 933a62da8a11bb9e57aa910852a68d8de271991d Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Tue, 1 Jul 2025 15:00:07 +0530 Subject: [PATCH 3/8] Defined a variable for static strings to reduce duplication --- .../Application-Settings/schedule.cy.js | 277 ++++++++++++------ 1 file changed, 189 insertions(+), 88 deletions(-) diff --git a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js index 5e22917a7f0..3721d4f2625 100644 --- a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js +++ b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js @@ -1,6 +1,94 @@ /* eslint-disable no-undef */ -function selectConfigMenu(configuration = 'Add a new Schedule') { +const textConstants = { + // List items + schedulesAccordionItem: 'Schedules', + + // Field values + initialScheduleName: 'Test name', + editedScheduleName: 'Dummy name', + initialDescription: 'Test description', + editedDescription: 'Dummy description', + actionTypeVmAnalysis: 'vm', + actionTypeTemplateAnalysis: 'miq_template', + actionTypeHostAnalysis: 'host', + actionTypeContainerAnalysis: 'container_image', + actionTypeClusterAnalysis: 'emscluster', + actionTypeDataStoreAnalysis: 'storage', + actionTypeVmCompilanceCheck: 'vm_check_compliance', + actionTypeHostCompilanceCheck: 'host_check_compliance', + actionTypeContainerCompilanceCheck: 'container_image_check_compliance', + actionTypeAutomationTasks: 'automation_request', + filterTypeVmCluster: 'cluster', + timerTypeOnce: 'Once', + timerTypeHourly: 'Hourly', + timerTypeDaily: 'Daily', + timerTypeWeekly: 'Weekly', + timerTypeMonthly: 'Monthly', + frequencyTypeHour: '1 Hour', + timezoneTypeHawaii: '(GMT-10:00) Hawaii', + initialStartDate: '06/30/2025', + editedStartDate: '07/21/2025', + startTime: '11:23', + + // Buttons + saveButton: 'Save', + cancelButton: 'Cancel', + resetButton: 'Reset', + + // Config options + addScheduleConfigOption: 'Add a new Schedule', + deleteScheduleConfigOption: 'Delete this Schedule from the Database', + editScheduleConfigOption: 'Edit this Schedule', + disableScheduleConfigOption: 'Disable this Schedule', + enableScheduleConfigOption: 'Enable this Schedule', + queueScheduleConfigOption: 'Queue up this Schedule to run now', + + // Menu options + settingsMenuOption: 'Settings', + appSettingsMenuOption: 'Application Settings', +}; + +const { + settingsMenuOption, + appSettingsMenuOption, + actionTypeVmAnalysis, + actionTypeTemplateAnalysis, + actionTypeHostAnalysis, + actionTypeContainerAnalysis, + actionTypeClusterAnalysis, + actionTypeDataStoreAnalysis, + actionTypeVmCompilanceCheck, + actionTypeHostCompilanceCheck, + actionTypeContainerCompilanceCheck, + actionTypeAutomationTasks, + timerTypeOnce, + timerTypeHourly, + timerTypeDaily, + timerTypeWeekly, + timerTypeMonthly, + cancelButton, + saveButton, + initialScheduleName, + editScheduleConfigOption, + editedScheduleName, + editedDescription, + editedStartDate, + resetButton, + initialDescription, + initialStartDate, + disableScheduleConfigOption, + enableScheduleConfigOption, + queueScheduleConfigOption, + addScheduleConfigOption, + frequencyTypeHour, + timezoneTypeHawaii, + startTime, + deleteScheduleConfigOption, + schedulesAccordionItem, +} = textConstants; + +function selectConfigMenu(configuration = addScheduleConfigOption) { cy.get('#miq_schedule_vmdb_choice').click(); cy.get(`ul[aria-label="Configuration"] [title="${configuration}"]`).click(); } @@ -10,31 +98,36 @@ function addSchedule() { // Checks if Save button is disabled initially cy.contains( '#main-content .bx--btn-set button[type="submit"]', - 'Save' + saveButton ).should('be.disabled'); // Adding data - cy.get('input#name').type('Test name'); - cy.get('input#description').type('Test description'); + cy.get('input#name').type(initialScheduleName); + cy.get('input#description').type(initialDescription); cy.get('input[type="checkbox"]#enabled').check({ force: true }); - cy.get('select#action_typ').select('VM Analysis'); - cy.get('select#filter_typ').select('A single VM'); - cy.get('select#timer_typ').select('Hourly'); - cy.get('select#timer_value').select('1 Hour'); + // Select Action type option: 'VM Analysis' + cy.get('select#action_typ').select(actionTypeVmAnalysis); + // Select Filter type option: 'A Single VM' + cy.get('select#filter_typ').select(actionTypeVmAnalysis); + // Select Run option: 'Hours' + cy.get('select#timer_typ').select(timerTypeHourly); + // Select Every option: '1 Hour' + cy.get('select#timer_value').select(frequencyTypeHour); + // Select Time zone option: '(GMT-10:00) Hawaii' cy.get('input[role="combobox"]#time_zone').click(); - cy.contains('[role="option"]', '(GMT-10:00) Hawaii') + cy.contains('[role="option"]', timezoneTypeHawaii) .should('be.visible') .click(); - cy.get('input#start_date').type('06/30/2025'); - cy.get('input#start_time').type('11:23'); + cy.get('input#start_date').type(initialStartDate); + cy.get('input#start_time').type(startTime); // Checks if Save button is enabled once all required fields are filled - cy.contains('#main-content .bx--btn-set button[type="submit"]', 'Save') + cy.contains('#main-content .bx--btn-set button[type="submit"]', saveButton) .should('be.enabled') .click(); } -function deleteSchedule(scheduleName = 'Test name') { +function deleteSchedule(scheduleName = initialScheduleName) { // Selecting the schedule - cy.contains('li.list-group-item', scheduleName).click(); + cy.accordionItem(scheduleName); // Listening for the browser confirm alert and confirming cy.listen_for_browser_confirm_alert(); selectConfigMenu(deleteScheduleConfigOption); @@ -45,12 +138,12 @@ function invokeCleanupDeletion() { // Iterate and clean up any leftover schedules created during the test cy.get('li.list-group-item').each(($el) => { const text = $el?.text()?.trim(); - if (text === 'Test name') { + if (text === initialScheduleName) { deleteSchedule(); return false; } - if (text === 'Dummy name') { - deleteSchedule('Dummy name'); + if (text === editedScheduleName) { + deleteSchedule(editedScheduleName); return false; } return true; @@ -74,7 +167,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.intercept('POST', '/ops/tree_select?id=xx-msc&text=Schedules').as( 'getSchedules' ); - cy.get('[title="Schedules"]').click(); + cy.accordionItem(schedulesAccordionItem); cy.wait('@getSchedules'); }); @@ -83,67 +176,73 @@ describe('Automate Schedule form operations: Settings > Application Settings > S /* ===== Selecting any option other than "Automation Tasks" from "Action" dropdown does not hide the Filter dropdown ===== */ - cy.get('select#action_typ').select('VM Analysis'); - cy.get('select#action_typ').should('have.value', 'vm'); + cy.get('select#action_typ').select(actionTypeVmAnalysis); + cy.get('select#action_typ').should('have.value', actionTypeVmAnalysis); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); - cy.get('select#action_typ').select('Template Analysis'); - cy.get('select#action_typ').should('have.value', 'miq_template'); + cy.get('select#action_typ').select(actionTypeTemplateAnalysis); + cy.get('select#action_typ').should( + 'have.value', + actionTypeTemplateAnalysis + ); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); - cy.get('select#action_typ').select('Host Analysis'); - cy.get('select#action_typ').should('have.value', 'host'); + cy.get('select#action_typ').select(actionTypeHostAnalysis); + cy.get('select#action_typ').should('have.value', actionTypeHostAnalysis); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); - cy.get('select#action_typ').select('Container Image Analysis'); - cy.get('select#action_typ').should('have.value', 'container_image'); + cy.get('select#action_typ').select(actionTypeContainerAnalysis); + cy.get('select#action_typ').should( + 'have.value', + actionTypeContainerAnalysis + ); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); - cy.get('select#action_typ').select('Cluster Analysis'); - cy.get('select#action_typ').should('have.value', 'emscluster'); + cy.get('select#action_typ').select(actionTypeClusterAnalysis); + cy.get('select#action_typ').should('have.value', actionTypeClusterAnalysis); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); - cy.get('select#action_typ').select('Datastore Analysis'); - cy.get('select#action_typ').should('have.value', 'storage'); + cy.get('select#action_typ').select(actionTypeDataStoreAnalysis); + cy.get('select#action_typ').should( + 'have.value', + actionTypeDataStoreAnalysis + ); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); - cy.get('select#action_typ').select('VM Compliance Check'); - cy.get('select#action_typ').should('have.value', 'vm_check_compliance'); + cy.get('select#action_typ').select(actionTypeVmCompilanceCheck); + cy.get('select#action_typ').should( + 'have.value', + actionTypeVmCompilanceCheck + ); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); - cy.get('select#action_typ').select('Host Compliance Check'); - cy.get('select#action_typ').should('have.value', 'host_check_compliance'); + cy.get('select#action_typ').select(actionTypeHostCompilanceCheck); + cy.get('select#action_typ').should( + 'have.value', + actionTypeHostCompilanceCheck + ); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); - cy.get('select#action_typ').select('Container Image Compliance Check'); + cy.get('select#action_typ').select(actionTypeContainerCompilanceCheck); cy.get('select#action_typ').should( 'have.value', - 'container_image_check_compliance' + actionTypeContainerCompilanceCheck ); // Checking for Filter type dropdown - cy.get('label[for="filter_typ"]').should('exist'); - cy.get('select#filter_typ').should('exist'); + verifyFilterTypeDropdownExists(); /* ===== Selecting "Automation Tasks" option from "Action" dropdown shows Zone, Object details & Object fields ===== */ - cy.get('select#action_typ').select('Automation Tasks'); - cy.get('select#action_typ').should('have.value', 'automation_request'); + cy.get('select#action_typ').select(actionTypeAutomationTasks); + cy.get('select#action_typ').should('have.value', actionTypeAutomationTasks); // Checking for Zone dropdown cy.get('label[for="zone_id"]').should('exist'); @@ -186,36 +285,35 @@ describe('Automate Schedule form operations: Settings > Application Settings > S /* ===== Selecting "Once" option from "Run" dropdown does not show the "Every" dropdown ===== */ - cy.get('select#timer_typ').select('Once'); + cy.get('select#timer_typ').select(timerTypeOnce); // Checking whether the Every dropdown is hidden cy.get('input#timer_value').should('not.exist'); /* ===== Selecting any other option other than "Once" from "Run" dropdown shows the "Every" dropdown ===== */ - cy.get('select#timer_typ').select('Hours'); + cy.get('select#timer_typ').select(timerTypeHourly); // Checking whether the "Every" dropdown exist - cy.get('label[for="timer_value"]').should('exist'); - cy.get('select#timer_value').should('exist'); + verifyTimerDropdownExists(); - cy.get('select#timer_typ').select('Days'); + cy.get('select#timer_typ').select(timerTypeDaily); // Checking whether the "Every" dropdown exist - cy.get('label[for="timer_value"]').should('exist'); - cy.get('select#timer_value').should('exist'); + verifyTimerDropdownExists(); - cy.get('select#timer_typ').select('Weeks'); + cy.get('select#timer_typ').select(timerTypeWeekly); // Checking whether the "Every" dropdown exist - cy.get('label[for="timer_value"]').should('exist'); - cy.get('select#timer_value').should('exist'); + verifyTimerDropdownExists(); - cy.get('select#timer_typ').select('Months'); + cy.get('select#timer_typ').select(timerTypeMonthly); // Checking whether the "Every" dropdown exist - cy.get('label[for="timer_value"]').should('exist'); - cy.get('select#timer_value').should('exist'); + verifyTimerDropdownExists(); }); it('Checking whether Cancel button works on the Add form', () => { selectConfigMenu(); - cy.contains('#main-content .bx--btn-set button[type="button"]', 'Cancel') + cy.contains( + '#main-content .bx--btn-set button[type="button"]', + cancelButton + ) .should('be.enabled') .click(); cy.expect_flash('success'); @@ -228,13 +326,13 @@ describe('Automate Schedule form operations: Settings > Application Settings > S /* ===== Editing a schedule ===== */ // Selecting the created schedule - cy.contains('li.list-group-item', 'Test name').click(); - selectConfigMenu('Edit this Schedule'); + cy.accordionItem(initialScheduleName); + selectConfigMenu(editScheduleConfigOption); // Editing name and description - cy.get('input#name').clear().type('Dummy name'); - cy.get('input#description').clear().type('Dummy description'); + cy.get('input#name').clear().type(editedScheduleName); + cy.get('input#description').clear().type(editedDescription); // Confirms Save button is enabled after making edits - cy.contains('#main-content .bx--btn-set button[type="submit"]', 'Save') + cy.contains('#main-content .bx--btn-set button[type="submit"]', saveButton) .should('be.enabled') .click(); cy.expect_flash('success'); @@ -248,30 +346,33 @@ describe('Automate Schedule form operations: Settings > Application Settings > S /* ===== Checking whether Cancel button works ===== */ // Selecting the created schedule - cy.contains('li.list-group-item', 'Test name').click(); - selectConfigMenu('Edit this Schedule'); - cy.contains('#main-content .bx--btn-set button[type="button"]', 'Cancel') + cy.accordionItem(initialScheduleName); + selectConfigMenu(editScheduleConfigOption); + cy.contains( + '#main-content .bx--btn-set button[type="button"]', + cancelButton + ) .should('be.enabled') .click(); cy.expect_flash('success'); /* ===== Checking whether Reset button works ===== */ // Selecting the created schedule - cy.contains('li.list-group-item', 'Test name').click(); - selectConfigMenu('Edit this Schedule'); + cy.accordionItem(initialScheduleName); + selectConfigMenu(editScheduleConfigOption); // Editing description and start date - cy.get('input#description').clear().type('Dummy description'); - cy.get('input#start_date').clear().type('07/21/2025'); - cy.contains('#main-content .bx--btn-set button[type="button"]', 'Reset') + cy.get('input#description').clear().type(editedDescription); + cy.get('input#start_date').clear().type(editedStartDate); + cy.contains('#main-content .bx--btn-set button[type="button"]', resetButton) .should('be.enabled') .click(); cy.expect_flash('warning'); // Confirming the edited fields contain the old values after resetting - cy.get('input#description').should('have.value', 'Test description'); - cy.get('input#start_date').should('have.value', '06/30/2025'); + cy.get('input#description').should('have.value', initialDescription); + cy.get('input#start_date').should('have.value', initialStartDate); // Selecting Schedules menu item to bypass a bug, can be removed once #9505 is merged - cy.get('[title="Schedules"]').click(); + cy.accordionItem(schedulesAccordionItem); }); it('Checking whether creating a duplicate record is restricted', () => { @@ -287,7 +388,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S /* ===== Adding a schedule ===== */ addSchedule(); // Selecting the created schedule - cy.contains('li.list-group-item', 'Test name').click(); + cy.accordionItem(initialScheduleName); /* ===== Disabling the schedule ===== */ selectConfigMenu(disableScheduleConfigOption); @@ -309,7 +410,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S invokeCleanupDeletion(); } else { // Navigate to Settings -> Application-Settings before looking out for Schedules created during test - cy.menu('Settings', 'Application Settings'); + cy.menu(settingsMenuOption, appSettingsMenuOption); invokeCleanupDeletion(); } }); From df5dcbc7e3650030de20cb382e0ef0f9861e3f3d Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Thu, 3 Jul 2025 23:09:13 +0530 Subject: [PATCH 4/8] Reworked browser-confirm command to validate alert presence --- .../Application-Settings/schedule.cy.js | 17 ++-- cypress/support/assertions/expect_alerts.js | 82 ++++++++++++------- 2 files changed, 65 insertions(+), 34 deletions(-) diff --git a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js index 3721d4f2625..5798cd6c411 100644 --- a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js +++ b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js @@ -89,8 +89,14 @@ const { } = textConstants; function selectConfigMenu(configuration = addScheduleConfigOption) { - cy.get('#miq_schedule_vmdb_choice').click(); - cy.get(`ul[aria-label="Configuration"] [title="${configuration}"]`).click(); + cy.get( + `.miq-toolbar-actions .miq-toolbar-group button[title="Configuration"]` + ).click(); + return cy + .get( + `ul#overflow-menu-1__menu-body button[title="${configuration}"][role="menuitem"]` + ) + .click(); } function addSchedule() { @@ -128,9 +134,10 @@ function addSchedule() { function deleteSchedule(scheduleName = initialScheduleName) { // Selecting the schedule cy.accordionItem(scheduleName); - // Listening for the browser confirm alert and confirming - cy.listen_for_browser_confirm_alert(); - selectConfigMenu(deleteScheduleConfigOption); + // Listening for the browser confirm alert and confirming deletion + cy.expect_browser_confirm_with_text({ + confirmTriggerFn: () => selectConfigMenu(deleteScheduleConfigOption), + }); cy.expect_flash('success'); } diff --git a/cypress/support/assertions/expect_alerts.js b/cypress/support/assertions/expect_alerts.js index 1c6c2ba3938..9a39ec1f8fb 100644 --- a/cypress/support/assertions/expect_alerts.js +++ b/cypress/support/assertions/expect_alerts.js @@ -1,49 +1,73 @@ /* eslint-disable no-undef */ +const flashClassMap = { + warning: 'warning', + error: 'danger', + info: 'info', + success: 'success', +}; + /** * Custom Cypress command to validate flash messages. * @param {string} alertType - Type of alert (success, warning, error, info). * @param {string} [containsText] - Optional text that the alert should contain. * @returns {Cypress.Chainable} - The alert element if found, or an assertion failure. */ -Cypress.Commands.add('expect_flash', (alertType = 'success', containsText) => { - let alertClassName; - switch (alertType) { - case 'warning': - alertClassName = 'warning'; - break; - case 'error': - alertClassName = 'danger'; - break; - case 'info': - alertClassName = 'info'; - break; - default: - alertClassName = 'success'; - break; - } - const alert = cy - .get(`#main_div #flash_msg_div .alert-${alertClassName}`) - .should('be.visible'); +Cypress.Commands.add( + 'expect_flash', + (flashType = flashClassMap.success, containsText) => { + const alertClassName = flashClassMap[flashType] || flashClassMap.success; + const alert = cy + .get(`#main_div #flash_msg_div .alert-${alertClassName}`) + .should('be.visible'); - if (containsText) { - return alert.should(($el) => { - const actualText = $el.text().toLowerCase(); - expect(actualText).to.include(containsText.toLowerCase()); - }); - } + if (containsText) { + return alert.should(($el) => { + const actualText = $el.text().toLowerCase(); + expect(actualText).to.include(containsText.toLowerCase()); + }); + } - return alert; -}); + return alert; + } +); +/** + * Custom Cypress command to validate browser confirm alerts. + * @param {Object} options - Options for the command. + * @param {Function} options.confirmTriggerFn - A function that triggers the confirm dialog. + * This function **must return a Cypress.Chainable**, like `cy.get(...).click()`, + * so that Cypress can properly wait and chain `.then()` afterward. + * @example + * cy.expectBrowserConfirm({ + * containsText: 'sure to proceed?', + * proceed: true, + * confirmTriggerFn: () => { + * return cy.get('[data-testid="delete"]').click() + * } + * }); + * @example + * cy.expectBrowserConfirm({ + * confirmTriggerFn: () => cy.contains('deleted').click() + * }); + * @param {string} [options.containsText] - Optional text that the confirm alert should contain. + * @param {boolean} [options.proceed=true] - Whether to proceed with the confirm (true = OK, false = Cancel). + */ Cypress.Commands.add( - 'listen_for_browser_confirm_alert', - (containsText, proceed = true) => { + 'expect_browser_confirm_with_text', + ({ confirmTriggerFn, containsText, proceed = true }) => { + let alertTriggered = false; cy.on('window:confirm', (actualText) => { + alertTriggered = true; if (containsText) { expect(actualText.toLowerCase()).to.include(containsText.toLowerCase()); } return proceed; // true = OK, false = Cancel }); + // Fires the event that triggers the confirm dialog + confirmTriggerFn().then(() => { + expect(alertTriggered, 'Expected browser confirm alert to be triggered') + .to.be.true; + }); } ); From a2c7783e28c8a8bf9dfed767c14fb377f1864ae5 Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Mon, 7 Jul 2025 13:32:43 +0530 Subject: [PATCH 5/8] Include flash message snippets for validation --- .../Application-Settings/schedule.cy.js | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js index 5798cd6c411..7c2e8545e1f 100644 --- a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js +++ b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js @@ -47,6 +47,25 @@ const textConstants = { // Menu options settingsMenuOption: 'Settings', appSettingsMenuOption: 'Application Settings', + + // Flash message types + flashTypeSuccess: 'success', + flashTypeWarning: 'warning', + flashTypeError: 'error', + flashTypeInfo: 'info', + + // Flash message text snippets + flashMessageScheduleQueued: 'queued to run', + flashMessageOperationCanceled: 'cancelled', + flashMessageScheduleDisabled: 'disabled', + flashMessageScheduleEnabled: 'enabled', + flashMessageScheduleSaved: 'saved', + flashMessageResetSchedule: 'reset', + flashMessageScheduleDeleted: 'delete successful', + flashMessageFailedToAddSchedule: 'failed', + + // Browser alert text snippets + browserAlertDeleteConfirmText: 'will be permanently removed', }; const { @@ -86,6 +105,19 @@ const { startTime, deleteScheduleConfigOption, schedulesAccordionItem, + flashTypeSuccess, + flashTypeWarning, + flashTypeError, + flashTypeInfo, + flashMessageScheduleQueued, + flashMessageOperationCanceled, + flashMessageScheduleDisabled, + flashMessageScheduleEnabled, + flashMessageScheduleSaved, + flashMessageResetSchedule, + flashMessageScheduleDeleted, + flashMessageFailedToAddSchedule, + browserAlertDeleteConfirmText, } = textConstants; function selectConfigMenu(configuration = addScheduleConfigOption) { @@ -137,8 +169,9 @@ function deleteSchedule(scheduleName = initialScheduleName) { // Listening for the browser confirm alert and confirming deletion cy.expect_browser_confirm_with_text({ confirmTriggerFn: () => selectConfigMenu(deleteScheduleConfigOption), + containsText: browserAlertDeleteConfirmText, }); - cy.expect_flash('success'); + cy.expect_flash(flashTypeSuccess, flashMessageScheduleDeleted); } function invokeCleanupDeletion() { @@ -323,13 +356,13 @@ describe('Automate Schedule form operations: Settings > Application Settings > S ) .should('be.enabled') .click(); - cy.expect_flash('success'); + cy.expect_flash(flashTypeSuccess, flashMessageOperationCanceled); }); it('Checking whether add, edit & delete schedule works', () => { /* ===== Adding a schedule ===== */ addSchedule(); - cy.expect_flash('success'); + cy.expect_flash(flashTypeSuccess, flashMessageScheduleSaved); /* ===== Editing a schedule ===== */ // Selecting the created schedule @@ -342,7 +375,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.contains('#main-content .bx--btn-set button[type="submit"]', saveButton) .should('be.enabled') .click(); - cy.expect_flash('success'); + cy.expect_flash(flashTypeSuccess, flashMessageScheduleSaved); /* ===== Delete is already handled from afterEach hook ===== */ }); @@ -361,7 +394,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S ) .should('be.enabled') .click(); - cy.expect_flash('success'); + cy.expect_flash(flashTypeSuccess, flashMessageOperationCanceled); /* ===== Checking whether Reset button works ===== */ // Selecting the created schedule @@ -373,7 +406,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.contains('#main-content .bx--btn-set button[type="button"]', resetButton) .should('be.enabled') .click(); - cy.expect_flash('warning'); + cy.expect_flash(flashTypeWarning, flashMessageResetSchedule); // Confirming the edited fields contain the old values after resetting cy.get('input#description').should('have.value', initialDescription); cy.get('input#start_date').should('have.value', initialStartDate); @@ -388,7 +421,7 @@ describe('Automate Schedule form operations: Settings > Application Settings > S /* ===== Trying to add the same schedule again ===== */ addSchedule(); - cy.expect_flash('error'); + cy.expect_flash(flashTypeError, flashMessageFailedToAddSchedule); }); it('Checking whether Disabling, Enabling & Queueing up the schedule works', () => { @@ -399,15 +432,15 @@ describe('Automate Schedule form operations: Settings > Application Settings > S /* ===== Disabling the schedule ===== */ selectConfigMenu(disableScheduleConfigOption); - cy.expect_flash('info'); + cy.expect_flash(flashTypeInfo, flashMessageScheduleDisabled); /* ===== Enabling the schedule ===== */ selectConfigMenu(enableScheduleConfigOption); - cy.expect_flash('info'); + cy.expect_flash(flashTypeInfo, flashMessageScheduleEnabled); /* ===== Queueing-up the schedule ===== */ selectConfigMenu(queueScheduleConfigOption); - cy.expect_flash('success'); + cy.expect_flash(flashTypeSuccess, flashMessageScheduleQueued); }); afterEach(() => { From 31b33e665de4c569e03ca3e6b890456618194644 Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Mon, 7 Jul 2025 13:57:25 +0530 Subject: [PATCH 6/8] Optimize cy.toolbar: remove extra iterations, ensure chainable return --- .../Application-Settings/schedule.cy.js | 13 ++++--------- cypress/support/commands/toolbar.js | 18 +++++++++++------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js index 7c2e8545e1f..7d7314d612f 100644 --- a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js +++ b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js @@ -37,6 +37,7 @@ const textConstants = { resetButton: 'Reset', // Config options + configToolbarButton: 'Configuration', addScheduleConfigOption: 'Add a new Schedule', deleteScheduleConfigOption: 'Delete this Schedule from the Database', editScheduleConfigOption: 'Edit this Schedule', @@ -105,6 +106,7 @@ const { startTime, deleteScheduleConfigOption, schedulesAccordionItem, + configToolbarButton, flashTypeSuccess, flashTypeWarning, flashTypeError, @@ -120,15 +122,8 @@ const { browserAlertDeleteConfirmText, } = textConstants; -function selectConfigMenu(configuration = addScheduleConfigOption) { - cy.get( - `.miq-toolbar-actions .miq-toolbar-group button[title="Configuration"]` - ).click(); - return cy - .get( - `ul#overflow-menu-1__menu-body button[title="${configuration}"][role="menuitem"]` - ) - .click(); +function selectConfigMenu(menuOption = addScheduleConfigOption) { + return cy.toolbar(configToolbarButton, menuOption); } function addSchedule() { diff --git a/cypress/support/commands/toolbar.js b/cypress/support/commands/toolbar.js index d5a09520835..6f98bf425f0 100644 --- a/cypress/support/commands/toolbar.js +++ b/cypress/support/commands/toolbar.js @@ -15,18 +15,22 @@ Cypress.Commands.add('toolbar', (toolbarButton, dropdownButton = '') => { }); if (dropdownButton) { - cy.get('.bx--overflow-menu-options').then((dropdownButtons) => { + return cy.get('.bx--overflow-menu-options').then((dropdownButtons) => { const buttons = dropdownButtons.children(); - const nums = [...Array(buttons.length).keys()]; - nums.forEach((index) => { + for (let index = 0; index < buttons.length; index++) { const button = buttons[index]; - if (button && button.innerText && button.innerText.includes(dropdownButton)) { - button.children[0].click(); - return; + if ( + button && + button.innerText && + button.innerText.includes(dropdownButton) + ) { + return cy.wrap(button.children[0]).click(); } - }); + } + return cy.wrap(null); }); } + return cy.wrap(null); }); // toolbarButton: String for the text of the toolbar button to click. From 62b3ad0faf8083a41e09789abec3208a4e953ba6 Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Mon, 7 Jul 2025 14:40:54 +0530 Subject: [PATCH 7/8] Add more API intercepts for better test stability --- .../Application-Settings/schedule.cy.js | 70 ++++++++++++++----- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js index 7d7314d612f..2503af6b5de 100644 --- a/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js +++ b/cypress/e2e/ui/Settings/Application-Settings/schedule.cy.js @@ -5,8 +5,8 @@ const textConstants = { schedulesAccordionItem: 'Schedules', // Field values - initialScheduleName: 'Test name', - editedScheduleName: 'Dummy name', + initialScheduleName: 'Test-name', + editedScheduleName: 'Dummy-name', initialDescription: 'Test description', editedDescription: 'Dummy description', actionTypeVmAnalysis: 'vm', @@ -152,15 +152,20 @@ function addSchedule() { .click(); cy.get('input#start_date').type(initialStartDate); cy.get('input#start_time').type(startTime); - // Checks if Save button is enabled once all required fields are filled + // Intercepting the API call for adding a new schedule + cy.intercept('POST', '/ops/schedule_edit/new?button=save').as( + 'addScheduleApi' + ); cy.contains('#main-content .bx--btn-set button[type="submit"]', saveButton) - .should('be.enabled') + .should('be.enabled') // Checks if Save button is enabled once all required fields are filled .click(); + // Wait for the API call to complete + cy.wait('@addScheduleApi'); } function deleteSchedule(scheduleName = initialScheduleName) { - // Selecting the schedule - cy.accordionItem(scheduleName); + // Selecting the schedule and intercepting the API call to get schedule details + interceptGetScheduleDetailsApi(scheduleName); // Listening for the browser confirm alert and confirming deletion cy.expect_browser_confirm_with_text({ confirmTriggerFn: () => selectConfigMenu(deleteScheduleConfigOption), @@ -169,6 +174,33 @@ function deleteSchedule(scheduleName = initialScheduleName) { cy.expect_flash(flashTypeSuccess, flashMessageScheduleDeleted); } +function interceptGetScheduleDetailsApi(scheduleName = initialScheduleName) { + // Flag to check if the request is fired + let requestFired = false; + // Intercepting the API call + cy.intercept( + { + method: 'POST', + pathname: '/ops/tree_select', + query: { text: scheduleName }, + }, + // This callback function will be called when the request is fired, + // from which the requestFired flag will be set to true + () => (requestFired = true) + ).as('getCreatedScheduleApi'); + // Triggering the action that will fire the API call, + // which is selecting the created schedule + cy.accordionItem(scheduleName); + // Wait for the API call to complete if it was fired + // This is to ensure that the test does not fail if the request was not fired + cy.then(() => { + // If the request was fired, wait for the alias + if (requestFired) { + cy.wait('@getCreatedScheduleApi'); + } + }); +} + function invokeCleanupDeletion() { // Iterate and clean up any leftover schedules created during the test cy.get('li.list-group-item').each(($el) => { @@ -199,9 +231,13 @@ describe('Automate Schedule form operations: Settings > Application Settings > S beforeEach(() => { cy.login(); cy.menu(settingsMenuOption, appSettingsMenuOption); - cy.intercept('POST', '/ops/tree_select?id=xx-msc&text=Schedules').as( - 'getSchedules' - ); + cy.intercept( + { + method: 'POST', + pathname: '/ops/tree_select', + query: { text: schedulesAccordionItem }, + }, + ).as('getSchedules'); cy.accordionItem(schedulesAccordionItem); cy.wait('@getSchedules'); }); @@ -360,8 +396,8 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.expect_flash(flashTypeSuccess, flashMessageScheduleSaved); /* ===== Editing a schedule ===== */ - // Selecting the created schedule - cy.accordionItem(initialScheduleName); + // Selecting the schedule and intercepting the API call to get schedule details + interceptGetScheduleDetailsApi(); selectConfigMenu(editScheduleConfigOption); // Editing name and description cy.get('input#name').clear().type(editedScheduleName); @@ -380,8 +416,8 @@ describe('Automate Schedule form operations: Settings > Application Settings > S addSchedule(); /* ===== Checking whether Cancel button works ===== */ - // Selecting the created schedule - cy.accordionItem(initialScheduleName); + // Selecting the schedule and intercepting the API call to get schedule details + interceptGetScheduleDetailsApi(); selectConfigMenu(editScheduleConfigOption); cy.contains( '#main-content .bx--btn-set button[type="button"]', @@ -392,8 +428,8 @@ describe('Automate Schedule form operations: Settings > Application Settings > S cy.expect_flash(flashTypeSuccess, flashMessageOperationCanceled); /* ===== Checking whether Reset button works ===== */ - // Selecting the created schedule - cy.accordionItem(initialScheduleName); + // Selecting the schedule and intercepting the API call to get schedule details + interceptGetScheduleDetailsApi(); selectConfigMenu(editScheduleConfigOption); // Editing description and start date cy.get('input#description').clear().type(editedDescription); @@ -422,8 +458,8 @@ describe('Automate Schedule form operations: Settings > Application Settings > S it('Checking whether Disabling, Enabling & Queueing up the schedule works', () => { /* ===== Adding a schedule ===== */ addSchedule(); - // Selecting the created schedule - cy.accordionItem(initialScheduleName); + // Selecting the schedule and intercepting the API call to get schedule details + interceptGetScheduleDetailsApi(); /* ===== Disabling the schedule ===== */ selectConfigMenu(disableScheduleConfigOption); From 09cdb2be2ef61378d692ab86834a9292794dcad2 Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Mon, 7 Jul 2025 22:47:54 +0530 Subject: [PATCH 8/8] Updated expect_flash variables: 'alert' -> 'flash' --- cypress/support/assertions/expect_alerts.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cypress/support/assertions/expect_alerts.js b/cypress/support/assertions/expect_alerts.js index 9a39ec1f8fb..938299a290b 100644 --- a/cypress/support/assertions/expect_alerts.js +++ b/cypress/support/assertions/expect_alerts.js @@ -9,26 +9,26 @@ const flashClassMap = { /** * Custom Cypress command to validate flash messages. - * @param {string} alertType - Type of alert (success, warning, error, info). - * @param {string} [containsText] - Optional text that the alert should contain. - * @returns {Cypress.Chainable} - The alert element if found, or an assertion failure. + * @param {string} flashType - Type of flash (success, warning, error, info). + * @param {string} [containsText] - Optional text that the flash-message should contain. + * @returns {Cypress.Chainable} - The flash-message element if found, or an assertion failure. */ Cypress.Commands.add( 'expect_flash', (flashType = flashClassMap.success, containsText) => { - const alertClassName = flashClassMap[flashType] || flashClassMap.success; - const alert = cy - .get(`#main_div #flash_msg_div .alert-${alertClassName}`) + const flashMessageClassName = flashClassMap[flashType] || flashClassMap.success; + const flashMessageElement = cy + .get(`#main_div #flash_msg_div .alert-${flashMessageClassName}`) .should('be.visible'); if (containsText) { - return alert.should(($el) => { + return flashMessageElement.should(($el) => { const actualText = $el.text().toLowerCase(); expect(actualText).to.include(containsText.toLowerCase()); }); } - return alert; + return flashMessageElement; } );