Skip to content

Commit c6fe6e2

Browse files
authored
Merge pull request #9635 from asirvadAbrahamVarghese/add-element-validation-commands
Add element validation commands
2 parents 68efec5 + 08b91c0 commit c6fe6e2

File tree

5 files changed

+335
-28
lines changed

5 files changed

+335
-28
lines changed

cypress/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ ManageIQ implements the following cypress extensions:
8080
* `cy.getFormSelectFieldById({ selectId })` - retrieves a form select field by its ID. `selectId` is the ID of the select field. e.g. `cy.getFormSelectFieldById({selectId: 'select-scan-limit'});`
8181
* `cy.getFormTextareaById({ textareaId })` - retrieves a form textarea field by its ID. `textareaId` is the ID of the textarea field. e.g. `cy.getFormTextareaById({textareaId: 'default.auth_key'});`
8282

83+
##### form_elements_validation_commands
84+
85+
* `cy.validateFormLabels(labelConfigs)` - validates form field labels based on provided configurations. `labelConfigs` is an array of label configuration objects with properties: `forValue` (required) - the 'for' attribute value of the label, `expectedText` (optional) - the expected text content of the label. e.g. `cy.validateFormLabels([{ forValue: 'name', expectedText: 'Name' }, { forValue: 'email', expectedText: 'Email Address' }]);` or using constants: `cy.validateFormLabels([{ [LABEL_CONFIG_KEYS.FOR_VALUE]: 'name', [LABEL_CONFIG_KEYS.EXPECTED_TEXT]: 'Name' }]);`
86+
* `cy.validateFormFields(fieldConfigs)` - validates form input fields based on provided configurations. `fieldConfigs` is an array of field configuration objects with properties: `id` (required) - the ID of the form field, `fieldType` (optional, default: 'input') - the type of field ('input', 'select', 'textarea'), `inputFieldType` (optional, default: 'text') - the type of input field ('text', 'password', 'number'), `shouldBeDisabled` (optional, default: false) - whether the field should be disabled, `expectedValue` (optional) - the expected value of the field. e.g. `cy.validateFormFields([{ id: 'name', shouldBeDisabled: true }, { id: 'role', fieldType: 'select', expectedValue: 'admin' }]);` or using constants: `cy.validateFormFields([{ [FIELD_CONFIG_KEYS.ID]: 'email', [FIELD_CONFIG_KEYS.INPUT_FIELD_TYPE]: 'email' }, { [FIELD_CONFIG_KEYS.ID]: 'name', [FIELD_CONFIG_KEYS.SHOULD_BE_DISABLED]: true }]);`
87+
* `cy.validateFormFooterButtons(buttonConfigs)` - validates form buttons based on provided configurations. `buttonConfigs` is an array of button configuration objects with properties: `buttonText` (required) - the text of the button, `buttonType` (optional, default: 'button') - the type of button (e.g., 'submit', 'reset'), `shouldBeDisabled` (optional, default: false) - whether the button should be disabled. e.g. `cy.validateFormFooterButtons([{ buttonText: 'Cancel' }, { buttonText: 'Submit', buttonType: 'submit', shouldBeDisabled: true }]);` or using constants: `cy.validateFormFooterButtons([{ [BUTTON_CONFIG_KEYS.TEXT]: 'Cancel' }]);`
88+
8389
#### Assertions
8490

8591
* `cy.expect_explorer_title(title)` - check that the title on an explorer screen matches the provided title. `title`: String for the title.

cypress/e2e/ui/Settings/Application-Settings/c_and_u_gap_collection.cy.js

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
/* eslint-disable no-undef */
2+
// TODO: Use aliased import(@cypress-dir) once #9631 is merged
23
import { flashClassMap } from '../../../../support/assertions/assertion_constants';
4+
import {
5+
LABEL_CONFIG_KEYS,
6+
FIELD_CONFIG_KEYS,
7+
BUTTON_CONFIG_KEYS,
8+
} from '../../../../support/commands/constants/command_constants';
39

410
// Menu options
511
const SETTINGS_MENU_OPTION = 'Settings';
@@ -74,34 +80,39 @@ describe('Automate C & U Gap Collection form operations: Settings > Application
7480
cy.contains('#main-content .bx--form h3', FORM_SUBHEADER_SNIPPET).should(
7581
'be.visible'
7682
);
77-
// Assert timezone label & field is visible and enabled
78-
cy.getFormLabelByForAttribute({ forValue: 'timezone' })
79-
.should('be.visible')
80-
.and('contain.text', TIMEZONE_FIELD_LABEL);
81-
cy.getFormInputFieldByIdAndType({ inputId: 'timezone' })
82-
.should('be.visible')
83-
.and('be.enabled');
84-
// Assert start date label & field is visible and enabled
85-
cy.getFormLabelByForAttribute({ forValue: 'startDate' })
86-
.should('be.visible')
87-
.and('contain.text', START_DATE_FIELD_LABEL);
88-
cy.getFormInputFieldByIdAndType({ inputId: 'startDate' })
89-
.should('be.visible')
90-
.and('be.enabled');
91-
// Assert end date label & field is visible and enabled
92-
cy.getFormLabelByForAttribute({ forValue: 'endDate' })
93-
.should('be.visible')
94-
.and('contain.text', END_DATE_FIELD_LABEL);
95-
cy.getFormInputFieldByIdAndType({ inputId: 'endDate' })
96-
.should('be.visible')
97-
.and('be.enabled');
98-
// Assert save button is visible and disabled
99-
cy.getFormFooterButtonByTypeWithText({
100-
buttonText: 'Save',
101-
buttonType: 'submit',
102-
})
103-
.should('be.visible')
104-
.and('be.disabled');
83+
84+
// Validate form labels
85+
cy.validateFormLabels([
86+
{
87+
[LABEL_CONFIG_KEYS.FOR_VALUE]: 'timezone',
88+
[LABEL_CONFIG_KEYS.EXPECTED_TEXT]: TIMEZONE_FIELD_LABEL,
89+
},
90+
{
91+
[LABEL_CONFIG_KEYS.FOR_VALUE]: 'startDate',
92+
[LABEL_CONFIG_KEYS.EXPECTED_TEXT]: START_DATE_FIELD_LABEL,
93+
},
94+
{
95+
[LABEL_CONFIG_KEYS.FOR_VALUE]: 'endDate',
96+
[LABEL_CONFIG_KEYS.EXPECTED_TEXT]: END_DATE_FIELD_LABEL,
97+
},
98+
]);
99+
// Validate form fields
100+
cy.validateFormFields([
101+
{
102+
[FIELD_CONFIG_KEYS.ID]: 'timezone',
103+
[FIELD_CONFIG_KEYS.EXPECTED_VALUE]: '(GMT+00:00) UTC',
104+
},
105+
{ [FIELD_CONFIG_KEYS.ID]: 'startDate' },
106+
{ [FIELD_CONFIG_KEYS.ID]: 'endDate' },
107+
]);
108+
// Validate form footer buttons
109+
cy.validateFormFooterButtons([
110+
{
111+
[BUTTON_CONFIG_KEYS.BUTTON_TEXT]: 'Save',
112+
[BUTTON_CONFIG_KEYS.BUTTON_TYPE]: 'submit',
113+
[BUTTON_CONFIG_KEYS.SHOULD_BE_DISABLED]: true,
114+
},
115+
]);
105116
});
106117

107118
it('Should fail if start date is greater than end date', () => {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Form elements validation constants
3+
* ==========================================================
4+
*/
5+
6+
// Form label configuration keys
7+
export const LABEL_CONFIG_KEYS = {
8+
FOR_VALUE: 'forValue',
9+
EXPECTED_TEXT: 'expectedText',
10+
};
11+
12+
// Form user input field configuration keys
13+
export const FIELD_CONFIG_KEYS = {
14+
ID: 'id',
15+
FIELD_TYPE: 'fieldType',
16+
INPUT_FIELD_TYPE: 'inputFieldType',
17+
SHOULD_BE_DISABLED: 'shouldBeDisabled',
18+
EXPECTED_VALUE: 'expectedValue',
19+
};
20+
21+
// Form field types
22+
export const FIELD_TYPES = {
23+
INPUT: 'input',
24+
SELECT: 'select',
25+
TEXTAREA: 'textarea',
26+
};
27+
28+
// Form button configuration keys
29+
export const BUTTON_CONFIG_KEYS = {
30+
BUTTON_TEXT: 'buttonText',
31+
BUTTON_TYPE: 'buttonType',
32+
SHOULD_BE_DISABLED: 'shouldBeDisabled',
33+
};
34+
35+
/* ========================================================== */
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
// TODO: Use aliased import(@cypress-dir) once #9631 is merged
2+
import {
3+
LABEL_CONFIG_KEYS,
4+
FIELD_CONFIG_KEYS,
5+
FIELD_TYPES,
6+
BUTTON_CONFIG_KEYS,
7+
} from './constants/command_constants.js';
8+
9+
/**
10+
* Helper function to validate that config objects only contain valid keys
11+
*
12+
* @param {Object} config - The configuration object to validate
13+
* @param {Object} validKeysObject - The object containing valid keys (e.g., LABEL_CONFIG_KEYS)
14+
* @param {string} configType - The type of configuration being validated (for error messages)
15+
*/
16+
const validateConfigKeys = (config, validKeysObject, configType) => {
17+
const validKeys = Object.values(validKeysObject);
18+
19+
Object.keys(config).forEach((key) => {
20+
if (!validKeys.includes(key)) {
21+
cy.logAndThrowError(
22+
`Unknown key "${key}" in ${configType} config. Valid keys are: ${validKeys.join(
23+
', '
24+
)}`
25+
);
26+
}
27+
});
28+
};
29+
30+
/**
31+
* Validates form field labels based on provided configurations
32+
*
33+
* @param {Array} labelConfigs - Array of label configuration objects
34+
* @param {string} labelConfigs[].forValue - The 'for' attribute value of the label
35+
* @param {string} [labelConfigs[].expectedText] - The expected text content of the label
36+
*
37+
* Example:
38+
* cy.validateFormLabels([
39+
* { [LABEL_CONFIG_KEYS.FOR_VALUE]: 'name', [LABEL_CONFIG_KEYS.EXPECTED_TEXT]: 'Name' },
40+
* { [LABEL_CONFIG_KEYS.FOR_VALUE]: 'email', [LABEL_CONFIG_KEYS.EXPECTED_TEXT]: 'Email Address' }
41+
* ]);
42+
*
43+
* Or using regular object keys:
44+
* cy.validateFormLabels([
45+
* { forValue: 'name', expectedText: 'Name' },
46+
* { forValue: 'email', expectedText: 'Email Address' }
47+
* ]);
48+
*
49+
* Both approaches work but using config-keys object(LABEL_CONFIG_KEYS) is recommended to avoid typos and unknown keys
50+
*/
51+
Cypress.Commands.add('validateFormLabels', (labelConfigs) => {
52+
if (!Array.isArray(labelConfigs)) {
53+
cy.logAndThrowError('labelConfigs must be an array');
54+
}
55+
56+
if (!labelConfigs.length) {
57+
cy.logAndThrowError('labelConfigs array cannot be empty');
58+
}
59+
60+
labelConfigs.forEach((config) => {
61+
validateConfigKeys(config, LABEL_CONFIG_KEYS, 'label');
62+
63+
const forValue = config[LABEL_CONFIG_KEYS.FOR_VALUE];
64+
const expectedText = config[LABEL_CONFIG_KEYS.EXPECTED_TEXT];
65+
66+
if (!forValue) {
67+
cy.logAndThrowError(
68+
`${LABEL_CONFIG_KEYS.FOR_VALUE} is required for each label config`
69+
);
70+
}
71+
72+
const labelCheck = cy
73+
.getFormLabelByForAttribute({ forValue })
74+
.should('be.visible');
75+
76+
if (expectedText) {
77+
labelCheck.and('contain.text', expectedText);
78+
}
79+
});
80+
});
81+
82+
/**
83+
* Validates form input fields based on provided configurations
84+
*
85+
* @param {Array} fieldConfigs - Array of field configuration objects
86+
* @param {string} fieldConfigs[].id - The ID of the form field
87+
* @param {string} [fieldConfigs[].fieldType='input'] - The type of field ('input', 'select', 'textarea')
88+
* @param {string} [fieldConfigs[].inputFieldType='text'] - The type of input field ('text', 'password', 'number')
89+
* @param {boolean} [fieldConfigs[].shouldBeDisabled=false] - Whether the field should be disabled
90+
* @param {string} [fieldConfigs[].expectedValue] - The expected value of the field
91+
*
92+
* Example:
93+
* cy.validateFormFields([
94+
* { [FIELD_CONFIG_KEYS.ID]: 'name', [FIELD_CONFIG_KEYS.SHOULD_BE_DISABLED]: true },
95+
* { [FIELD_CONFIG_KEYS.ID]: 'email', [FIELD_CONFIG_KEYS.INPUT_FIELD_TYPE]: 'email' },
96+
* {
97+
* [FIELD_CONFIG_KEYS.ID]: 'role',
98+
* [FIELD_CONFIG_KEYS.FIELD_TYPE]: FIELD_TYPES.SELECT,
99+
* [FIELD_CONFIG_KEYS.EXPECTED_VALUE]: 'admin'
100+
* }
101+
* ]);
102+
*
103+
* Or using regular object keys:
104+
* cy.validateFormFields([
105+
* { id: 'name', shouldBeDisabled: true },
106+
* { id: 'email' },
107+
* { id: 'role', fieldType: 'select', expectedValue: 'admin' }
108+
* ]);
109+
*
110+
* Both approaches work but using config-keys object(FIELD_CONFIG_KEYS) is recommended to avoid typos and unknown keys
111+
*/
112+
Cypress.Commands.add('validateFormFields', (fieldConfigs) => {
113+
if (!Array.isArray(fieldConfigs)) {
114+
cy.logAndThrowError('fieldConfigs must be an array');
115+
}
116+
117+
if (!fieldConfigs.length) {
118+
cy.logAndThrowError('fieldConfigs array cannot be empty');
119+
}
120+
121+
fieldConfigs.forEach((config) => {
122+
validateConfigKeys(config, FIELD_CONFIG_KEYS, 'field');
123+
124+
const id = config[FIELD_CONFIG_KEYS.ID];
125+
const fieldType = config[FIELD_CONFIG_KEYS.FIELD_TYPE] || FIELD_TYPES.INPUT;
126+
const inputFieldType = config[FIELD_CONFIG_KEYS.INPUT_FIELD_TYPE] || 'text';
127+
const shouldBeDisabled =
128+
config[FIELD_CONFIG_KEYS.SHOULD_BE_DISABLED] || false;
129+
const expectedValue = config[FIELD_CONFIG_KEYS.EXPECTED_VALUE];
130+
131+
if (!id) {
132+
cy.logAndThrowError(
133+
`${FIELD_CONFIG_KEYS.ID} is required for each field config`
134+
);
135+
}
136+
137+
// Check field based on type
138+
switch (fieldType) {
139+
case FIELD_TYPES.INPUT:
140+
cy.getFormInputFieldByIdAndType({
141+
inputId: id,
142+
inputType: inputFieldType,
143+
})
144+
.should('be.visible')
145+
.then((field) => {
146+
if (shouldBeDisabled) {
147+
expect(field).to.be.disabled;
148+
} else {
149+
expect(field).to.not.be.disabled;
150+
}
151+
152+
if (expectedValue) {
153+
cy.wrap(field).should('have.value', expectedValue);
154+
}
155+
});
156+
break;
157+
case FIELD_TYPES.SELECT:
158+
cy.getFormSelectFieldById({ selectId: id })
159+
.should('be.visible')
160+
.then((field) => {
161+
if (shouldBeDisabled) {
162+
expect(field).to.be.disabled;
163+
} else {
164+
expect(field).to.not.be.disabled;
165+
}
166+
167+
if (expectedValue) {
168+
cy.wrap(field).should('have.value', expectedValue);
169+
}
170+
});
171+
break;
172+
case FIELD_TYPES.TEXTAREA:
173+
cy.getFormTextareaById({ textareaId: id })
174+
.should('be.visible')
175+
.then((field) => {
176+
if (shouldBeDisabled) {
177+
expect(field).to.be.disabled;
178+
} else {
179+
expect(field).to.not.be.disabled;
180+
}
181+
182+
if (expectedValue) {
183+
cy.wrap(field).should('have.value', expectedValue);
184+
}
185+
});
186+
break;
187+
188+
default:
189+
cy.logAndThrowError(`Unsupported field type: ${fieldType}`);
190+
}
191+
});
192+
});
193+
194+
/**
195+
* Validates form buttons based on provided configurations
196+
*
197+
* @param {Array} buttonConfigs - Array of button configuration objects
198+
* @param {string} buttonConfigs[].buttonText - The text of the button
199+
* @param {string} [buttonConfigs[].buttonType='button'] - The type of button (e.g., 'submit', 'reset')
200+
* @param {boolean} [buttonConfigs[].shouldBeDisabled=false] - Whether the button should be disabled
201+
*
202+
* Example:
203+
* cy.validateFormFooterButtons([
204+
* { [BUTTON_CONFIG_KEYS.BUTTON_TEXT]: 'Cancel' },
205+
* { [BUTTON_CONFIG_KEYS.BUTTON_TEXT]: 'Reset', [BUTTON_CONFIG_KEYS.SHOULD_BE_DISABLED]: true },
206+
* { [BUTTON_CONFIG_KEYS.BUTTON_TEXT]: 'Submit', [BUTTON_CONFIG_KEYS.BUTTON_TYPE]: 'submit' }
207+
* ]);
208+
*
209+
* Or using regular object keys:
210+
* cy.validateFormFooterButtons([
211+
* { buttonText: 'Cancel' },
212+
* { buttonText: 'Reset', shouldBeDisabled: true },
213+
* { buttonText: 'Submit', buttonType: 'submit' }
214+
* ]);
215+
*
216+
* Both approaches work but using config-keys object(BUTTON_CONFIG_KEYS) is recommended to avoid typos and unknown keys
217+
*/
218+
Cypress.Commands.add('validateFormFooterButtons', (buttonConfigs) => {
219+
if (!Array.isArray(buttonConfigs)) {
220+
cy.logAndThrowError('buttonConfigs must be an array');
221+
}
222+
223+
if (!buttonConfigs.length) {
224+
cy.logAndThrowError('buttonConfigs array cannot be empty');
225+
}
226+
227+
buttonConfigs.forEach((config) => {
228+
validateConfigKeys(config, BUTTON_CONFIG_KEYS, 'button');
229+
230+
const buttonText = config[BUTTON_CONFIG_KEYS.BUTTON_TEXT];
231+
const buttonType = config[BUTTON_CONFIG_KEYS.BUTTON_TYPE] || 'button';
232+
const shouldBeDisabled =
233+
config[BUTTON_CONFIG_KEYS.SHOULD_BE_DISABLED] || false;
234+
235+
if (!buttonText) {
236+
cy.logAndThrowError(
237+
`${BUTTON_CONFIG_KEYS.BUTTON_TEXT} is required for each button config`
238+
);
239+
}
240+
241+
const buttonCheck = cy
242+
.getFormFooterButtonByTypeWithText({
243+
buttonText,
244+
buttonType,
245+
})
246+
.should('be.visible');
247+
248+
if (shouldBeDisabled) {
249+
buttonCheck.and('be.disabled');
250+
} else {
251+
buttonCheck.and('be.enabled');
252+
}
253+
});
254+
});

cypress/support/e2e.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import './commands/api_commands.js';
4444
import './commands/custom_logging_commands.js';
4545
import './commands/element_selectors.js';
4646
import './commands/explorer.js';
47+
import './commands/form_elements_validation_commands.js';
4748
import './commands/gtl.js';
4849
import './commands/login.js';
4950
import './commands/menu.js';

0 commit comments

Comments
 (0)