-
Notifications
You must be signed in to change notification settings - Fork 365
Add element validation commands #9635
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jrafanie
merged 5 commits into
ManageIQ:master
from
asirvadAbrahamVarghese:add-element-validation-commands
Oct 15, 2025
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
4f98999
Added command to validate form labels
asirvadAbrahamVarghese 948fb0f
Added command to validate form user-input fields
asirvadAbrahamVarghese 63ca416
Added command to validate form footer buttons
asirvadAbrahamVarghese 594d490
Updated readme with form_elements_validation_commands info
asirvadAbrahamVarghese 08b91c0
C&U gap collection form elements validation via commands
asirvadAbrahamVarghese File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| * Form elements validation constants | ||
| * ========================================================== | ||
| */ | ||
|
|
||
| // Form label configuration keys | ||
| export const LABEL_CONFIG_KEYS = { | ||
| FOR_VALUE: 'forValue', | ||
| EXPECTED_TEXT: 'expectedText', | ||
| }; | ||
|
|
||
| // Form user input field configuration keys | ||
| export const FIELD_CONFIG_KEYS = { | ||
| ID: 'id', | ||
| FIELD_TYPE: 'fieldType', | ||
| INPUT_FIELD_TYPE: 'inputFieldType', | ||
| SHOULD_BE_DISABLED: 'shouldBeDisabled', | ||
| EXPECTED_VALUE: 'expectedValue', | ||
| }; | ||
|
|
||
| // Form field types | ||
| export const FIELD_TYPES = { | ||
| INPUT: 'input', | ||
| SELECT: 'select', | ||
| TEXTAREA: 'textarea', | ||
| }; | ||
|
|
||
| // Form button configuration keys | ||
| export const BUTTON_CONFIG_KEYS = { | ||
| BUTTON_TEXT: 'buttonText', | ||
| BUTTON_TYPE: 'buttonType', | ||
| SHOULD_BE_DISABLED: 'shouldBeDisabled', | ||
| }; | ||
|
|
||
| /* ========================================================== */ |
254 changes: 254 additions & 0 deletions
254
cypress/support/commands/form_elements_validation_commands.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,254 @@ | ||
| // TODO: Use aliased import(@cypress-dir) once #9631 is merged | ||
| import { | ||
| LABEL_CONFIG_KEYS, | ||
| FIELD_CONFIG_KEYS, | ||
| FIELD_TYPES, | ||
| BUTTON_CONFIG_KEYS, | ||
| } from './constants/command_constants.js'; | ||
|
|
||
| /** | ||
| * Helper function to validate that config objects only contain valid keys | ||
| * | ||
| * @param {Object} config - The configuration object to validate | ||
| * @param {Object} validKeysObject - The object containing valid keys (e.g., LABEL_CONFIG_KEYS) | ||
| * @param {string} configType - The type of configuration being validated (for error messages) | ||
| */ | ||
| const validateConfigKeys = (config, validKeysObject, configType) => { | ||
| const validKeys = Object.values(validKeysObject); | ||
|
|
||
| Object.keys(config).forEach((key) => { | ||
| if (!validKeys.includes(key)) { | ||
| cy.logAndThrowError( | ||
| `Unknown key "${key}" in ${configType} config. Valid keys are: ${validKeys.join( | ||
| ', ' | ||
| )}` | ||
| ); | ||
| } | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * Validates form field labels based on provided configurations | ||
| * | ||
| * @param {Array} labelConfigs - Array of label configuration objects | ||
| * @param {string} labelConfigs[].forValue - The 'for' attribute value of the label | ||
| * @param {string} [labelConfigs[].expectedText] - The expected text content of the label | ||
| * | ||
| * Example: | ||
| * cy.validateFormLabels([ | ||
| * { [LABEL_CONFIG_KEYS.FOR_VALUE]: 'name', [LABEL_CONFIG_KEYS.EXPECTED_TEXT]: 'Name' }, | ||
| * { [LABEL_CONFIG_KEYS.FOR_VALUE]: 'email', [LABEL_CONFIG_KEYS.EXPECTED_TEXT]: 'Email Address' } | ||
| * ]); | ||
| * | ||
| * Or using regular object keys: | ||
| * cy.validateFormLabels([ | ||
| * { forValue: 'name', expectedText: 'Name' }, | ||
| * { forValue: 'email', expectedText: 'Email Address' } | ||
| * ]); | ||
| * | ||
| * Both approaches work but using config-keys object(LABEL_CONFIG_KEYS) is recommended to avoid typos and unknown keys | ||
| */ | ||
| Cypress.Commands.add('validateFormLabels', (labelConfigs) => { | ||
| if (!Array.isArray(labelConfigs)) { | ||
| cy.logAndThrowError('labelConfigs must be an array'); | ||
| } | ||
|
|
||
| if (!labelConfigs.length) { | ||
| cy.logAndThrowError('labelConfigs array cannot be empty'); | ||
| } | ||
|
|
||
| labelConfigs.forEach((config) => { | ||
| validateConfigKeys(config, LABEL_CONFIG_KEYS, 'label'); | ||
|
|
||
| const forValue = config[LABEL_CONFIG_KEYS.FOR_VALUE]; | ||
| const expectedText = config[LABEL_CONFIG_KEYS.EXPECTED_TEXT]; | ||
|
|
||
| if (!forValue) { | ||
| cy.logAndThrowError( | ||
| `${LABEL_CONFIG_KEYS.FOR_VALUE} is required for each label config` | ||
| ); | ||
| } | ||
|
|
||
| const labelCheck = cy | ||
| .getFormLabelByForAttribute({ forValue }) | ||
| .should('be.visible'); | ||
|
|
||
| if (expectedText) { | ||
| labelCheck.and('contain.text', expectedText); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * Validates form input fields based on provided configurations | ||
| * | ||
| * @param {Array} fieldConfigs - Array of field configuration objects | ||
| * @param {string} fieldConfigs[].id - The ID of the form field | ||
| * @param {string} [fieldConfigs[].fieldType='input'] - The type of field ('input', 'select', 'textarea') | ||
| * @param {string} [fieldConfigs[].inputFieldType='text'] - The type of input field ('text', 'password', 'number') | ||
| * @param {boolean} [fieldConfigs[].shouldBeDisabled=false] - Whether the field should be disabled | ||
| * @param {string} [fieldConfigs[].expectedValue] - The expected value of the field | ||
| * | ||
| * Example: | ||
| * cy.validateFormFields([ | ||
| * { [FIELD_CONFIG_KEYS.ID]: 'name', [FIELD_CONFIG_KEYS.SHOULD_BE_DISABLED]: true }, | ||
| * { [FIELD_CONFIG_KEYS.ID]: 'email', [FIELD_CONFIG_KEYS.INPUT_FIELD_TYPE]: 'email' }, | ||
| * { | ||
| * [FIELD_CONFIG_KEYS.ID]: 'role', | ||
| * [FIELD_CONFIG_KEYS.FIELD_TYPE]: FIELD_TYPES.SELECT, | ||
| * [FIELD_CONFIG_KEYS.EXPECTED_VALUE]: 'admin' | ||
| * } | ||
| * ]); | ||
| * | ||
| * Or using regular object keys: | ||
| * cy.validateFormFields([ | ||
| * { id: 'name', shouldBeDisabled: true }, | ||
| * { id: 'email' }, | ||
| * { id: 'role', fieldType: 'select', expectedValue: 'admin' } | ||
| * ]); | ||
| * | ||
| * Both approaches work but using config-keys object(FIELD_CONFIG_KEYS) is recommended to avoid typos and unknown keys | ||
| */ | ||
| Cypress.Commands.add('validateFormFields', (fieldConfigs) => { | ||
| if (!Array.isArray(fieldConfigs)) { | ||
| cy.logAndThrowError('fieldConfigs must be an array'); | ||
| } | ||
|
|
||
| if (!fieldConfigs.length) { | ||
| cy.logAndThrowError('fieldConfigs array cannot be empty'); | ||
| } | ||
|
|
||
| fieldConfigs.forEach((config) => { | ||
| validateConfigKeys(config, FIELD_CONFIG_KEYS, 'field'); | ||
|
|
||
| const id = config[FIELD_CONFIG_KEYS.ID]; | ||
| const fieldType = config[FIELD_CONFIG_KEYS.FIELD_TYPE] || FIELD_TYPES.INPUT; | ||
| const inputFieldType = config[FIELD_CONFIG_KEYS.INPUT_FIELD_TYPE] || 'text'; | ||
| const shouldBeDisabled = | ||
| config[FIELD_CONFIG_KEYS.SHOULD_BE_DISABLED] || false; | ||
| const expectedValue = config[FIELD_CONFIG_KEYS.EXPECTED_VALUE]; | ||
|
|
||
| if (!id) { | ||
| cy.logAndThrowError( | ||
| `${FIELD_CONFIG_KEYS.ID} is required for each field config` | ||
| ); | ||
| } | ||
|
|
||
| // Check field based on type | ||
| switch (fieldType) { | ||
| case FIELD_TYPES.INPUT: | ||
| cy.getFormInputFieldByIdAndType({ | ||
| inputId: id, | ||
| inputType: inputFieldType, | ||
| }) | ||
| .should('be.visible') | ||
| .then((field) => { | ||
| if (shouldBeDisabled) { | ||
| expect(field).to.be.disabled; | ||
| } else { | ||
| expect(field).to.not.be.disabled; | ||
| } | ||
|
|
||
| if (expectedValue) { | ||
| cy.wrap(field).should('have.value', expectedValue); | ||
| } | ||
| }); | ||
| break; | ||
| case FIELD_TYPES.SELECT: | ||
| cy.getFormSelectFieldById({ selectId: id }) | ||
| .should('be.visible') | ||
| .then((field) => { | ||
| if (shouldBeDisabled) { | ||
| expect(field).to.be.disabled; | ||
| } else { | ||
| expect(field).to.not.be.disabled; | ||
| } | ||
|
|
||
| if (expectedValue) { | ||
| cy.wrap(field).should('have.value', expectedValue); | ||
| } | ||
| }); | ||
| break; | ||
| case FIELD_TYPES.TEXTAREA: | ||
| cy.getFormTextareaById({ textareaId: id }) | ||
| .should('be.visible') | ||
| .then((field) => { | ||
| if (shouldBeDisabled) { | ||
| expect(field).to.be.disabled; | ||
| } else { | ||
| expect(field).to.not.be.disabled; | ||
| } | ||
|
|
||
| if (expectedValue) { | ||
| cy.wrap(field).should('have.value', expectedValue); | ||
| } | ||
| }); | ||
| break; | ||
|
|
||
| default: | ||
| cy.logAndThrowError(`Unsupported field type: ${fieldType}`); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * Validates form buttons based on provided configurations | ||
| * | ||
| * @param {Array} buttonConfigs - Array of button configuration objects | ||
| * @param {string} buttonConfigs[].buttonText - The text of the button | ||
| * @param {string} [buttonConfigs[].buttonType='button'] - The type of button (e.g., 'submit', 'reset') | ||
| * @param {boolean} [buttonConfigs[].shouldBeDisabled=false] - Whether the button should be disabled | ||
| * | ||
| * Example: | ||
| * cy.validateFormFooterButtons([ | ||
| * { [BUTTON_CONFIG_KEYS.BUTTON_TEXT]: 'Cancel' }, | ||
| * { [BUTTON_CONFIG_KEYS.BUTTON_TEXT]: 'Reset', [BUTTON_CONFIG_KEYS.SHOULD_BE_DISABLED]: true }, | ||
| * { [BUTTON_CONFIG_KEYS.BUTTON_TEXT]: 'Submit', [BUTTON_CONFIG_KEYS.BUTTON_TYPE]: 'submit' } | ||
| * ]); | ||
| * | ||
| * Or using regular object keys: | ||
| * cy.validateFormFooterButtons([ | ||
| * { buttonText: 'Cancel' }, | ||
| * { buttonText: 'Reset', shouldBeDisabled: true }, | ||
| * { buttonText: 'Submit', buttonType: 'submit' } | ||
| * ]); | ||
| * | ||
| * Both approaches work but using config-keys object(BUTTON_CONFIG_KEYS) is recommended to avoid typos and unknown keys | ||
| */ | ||
| Cypress.Commands.add('validateFormFooterButtons', (buttonConfigs) => { | ||
| if (!Array.isArray(buttonConfigs)) { | ||
| cy.logAndThrowError('buttonConfigs must be an array'); | ||
| } | ||
|
|
||
| if (!buttonConfigs.length) { | ||
| cy.logAndThrowError('buttonConfigs array cannot be empty'); | ||
| } | ||
|
|
||
| buttonConfigs.forEach((config) => { | ||
| validateConfigKeys(config, BUTTON_CONFIG_KEYS, 'button'); | ||
|
|
||
| const buttonText = config[BUTTON_CONFIG_KEYS.BUTTON_TEXT]; | ||
| const buttonType = config[BUTTON_CONFIG_KEYS.BUTTON_TYPE] || 'button'; | ||
| const shouldBeDisabled = | ||
| config[BUTTON_CONFIG_KEYS.SHOULD_BE_DISABLED] || false; | ||
|
|
||
| if (!buttonText) { | ||
| cy.logAndThrowError( | ||
| `${BUTTON_CONFIG_KEYS.BUTTON_TEXT} is required for each button config` | ||
| ); | ||
| } | ||
|
|
||
| const buttonCheck = cy | ||
| .getFormFooterButtonByTypeWithText({ | ||
| buttonText, | ||
| buttonType, | ||
| }) | ||
| .should('be.visible'); | ||
|
|
||
| if (shouldBeDisabled) { | ||
| buttonCheck.and('be.disabled'); | ||
| } else { | ||
| buttonCheck.and('be.enabled'); | ||
| } | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to be honest, this is hard for me to read. I would expect the validations to be an array of key, value pairs where the key should be the identifier/locator for the lookup, and the value is the expected value. Maybe we should in-line the constant values here so it's clear what the test is doing. In other words, what we're hiding in constants here is directly relevant to the test I think it's fine to have some duplication. There's no need to abstract them away or add a layer of indirection.
What do they all have in common? A CSS selector or similar and a contain.text assertion. Maybe an array of key/value pairs where the key is the selector and the value is the text to be asserted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we just turn this into a simple object with selectors/locators(id, for...) as keys and expected text as values, it’d look something like this:
The issue here is that when we need to support additional options(like in the other 2 commands
validateFormFields&validateFormFooterButtons), the only way to do so is by adding a delimiter (like - or |) in the value string(which currently only contains the expected text) and then do the other options logic in the command, which feels quite messy to me.For example, if we want to scroll the label element into view before making assertions, we’d have to write something like:
cy.validateFormLabels({ timezone: Timezone | scrollIntoView' });But with our current structure we can just add a new key:
If we're referring to the config-keys object(
LABEL_CONFIG_KEYS), its main purpose is to prevent the use of unknown or misspelled keys. For example, if I am using "expectedVale" instead of "expectedValue":The test will still run without throwing any errors, unless we explicitly validate that every key passed to the command exists in the config-keys object(
LABEL_CONFIG_KEYS).Let me know if anything stands out or needs tweaking...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, good point.. I'm not sure what's preferable... the current interface is hard to reason about for me right now. I feel like the tester should know what fields it wants to validate... if they typo the field, we should assert it's wrong so they can fix it. I "think" class/ids in the dom are less likely to change than message catalogs / strings so I'm fine with the repetitive nature of putting the field id/class/selector in the key. I'm not sure.
I think if you assert a field has a value and the field doesn't exist, we should raise an error.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cypress will fail if the field isn’t found (like if the ID is incorrect):

I was referring to configuration keys (like

forValue,expectedValue). Earlier, typos in these keys wouldn't cause any errors, the test would still execute. I've now added validation to catch unrecognised keys upfront.