-
Notifications
You must be signed in to change notification settings - Fork 365
[WIP] - Added automated tests with cypress for Namespace form #9517
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,323 @@ | ||
/* eslint-disable no-undef */ | ||
import { flashClassMap } from '../../../../../support/assertions/assertion_constants'; | ||
|
||
// Menu options | ||
const AUTOMATION_MENU_OPTION = 'Automation'; | ||
const EMBEDDED_AUTOMATION_MENU_OPTION = 'Embedded Automate'; | ||
const EXPLORER_MENU_OPTION = 'Explorer'; | ||
|
||
// Toolbar options | ||
const TOOLBAR_CONFIGURATION = 'Configuration'; | ||
const TOOLBAR_ADD_NEW_DOMAIN = 'Add a New Domain'; | ||
const TOOLBAR_ADD_NEW_NAMESPACE = 'Add a New Namespace'; | ||
const TOOLBAR_EDIT_NAMESPACE = 'Edit this Namespace'; | ||
const TOOLBAR_REMOVE_NAMESPACE = 'Remove this Namespace'; | ||
|
||
// Field values | ||
const NAME_SAPCE_PATH_FIELD_LABEL = 'Fully Qualified Name'; | ||
const NAME_FIELD_LABEL = 'Name'; | ||
const DESCRIPTION_FIELD_LABEL = 'Description'; | ||
const DOMAIN_NAME = 'Test_Domain'; | ||
const DESCRIPTION = 'Test description'; | ||
const NAMESPACE_NAME = 'Test_Namespace'; | ||
const EDITED_NAMESPACE_NAME = 'Test_Namespace_Edited'; | ||
const EDITED_DESCRIPTION = 'Test description edited'; | ||
const INVALID_NAMESPACE_NAME = 'Test Namespace'; | ||
const NAMESPACE_FORM_HEADER = 'Automate Namespace'; | ||
const NAMESPACE_FORM_SUB_HEADER = 'Info'; | ||
|
||
// List items | ||
const DATA_STORE_ACCORDION_LABEL = 'Datastore'; | ||
|
||
// Buttons | ||
const ADD_BUTTON_TEXT = 'Add'; | ||
const CANCEL_BUTTON_TEXT = 'Cancel'; | ||
const SAVE_BUTTON_TEXT = 'Save'; | ||
const RESET_BUTTON_TEXT = 'Reset'; | ||
|
||
// Flash message text snippets | ||
const FLASH_MESSAGE_ADD_SUCCESS = 'added'; | ||
const FLASH_MESSAGE_SAVE_SUCCESS = 'saved'; | ||
const FLASH_MESSAGE_CANCELLED = 'cancel'; | ||
const FLASH_MESSAGE_INVALID_NAMESPACE = 'contain only alphanumeric'; | ||
const FLASH_MESSAGE_NAMESPACE_REMOVED = 'delete successful'; | ||
const FLASH_MESSAGE_NAME_ALREADY_EXISTS = 'taken'; | ||
const FLASH_MESSAGE_RESET_NAMESPACE = 'reset'; | ||
const BROWSER_CONFIRM_REMOVE_MESSAGE = 'remove'; | ||
|
||
function addDomainOrNamespace({ | ||
nameFieldValue, | ||
afterDomainOrNamespaceCreation = () => {}, | ||
}) { | ||
// Adding name & description | ||
cy.getFormInputFieldById('name').type(nameFieldValue); | ||
cy.getFormInputFieldById('description').type(DESCRIPTION); | ||
// Submitting the form | ||
cy.interceptApi({ | ||
alias: 'addDomainOrNamespaceApi', | ||
urlPattern: '/miq_ae_class/create_namespace/new?button=add', | ||
triggerFn: () => | ||
cy.getFormFooterButtonByType(ADD_BUTTON_TEXT, 'submit').click(), | ||
onApiResponse: afterDomainOrNamespaceCreation, | ||
}); | ||
} | ||
|
||
function selectAccordionElement(accordionItemLabel) { | ||
const pathToTargetNode = | ||
accordionItemLabel === NAMESPACE_NAME | ||
? [DOMAIN_NAME, NAMESPACE_NAME] | ||
: [DOMAIN_NAME]; | ||
cy.interceptApi({ | ||
alias: 'treeSelectApi', | ||
urlPattern: /\/miq_ae_class\/tree_select\?id=.*&text=.*/, | ||
triggerFn: () => | ||
cy.selectAccordionItem([DATA_STORE_ACCORDION_LABEL, ...pathToTargetNode]), | ||
}); | ||
} | ||
|
||
function validateNamespaceFormFields(isEditForm = false) { | ||
// Assert form header is visible | ||
cy.expect_explorer_title(NAMESPACE_FORM_HEADER); | ||
// Assert sub header is visible | ||
cy.get('#main-content #datastore-form-wrapper h3').contains( | ||
NAMESPACE_FORM_SUB_HEADER | ||
); | ||
// Assert name-space path field label is visible | ||
cy.getFormLabelByInputId('namespacePath') | ||
.should('be.visible') | ||
.and('contain.text', NAME_SAPCE_PATH_FIELD_LABEL); | ||
// Assert name-space path field is visible and disabled | ||
cy.getFormInputFieldById('namespacePath') | ||
.should('be.visible') | ||
.and('be.disabled') | ||
.invoke('val') | ||
.should('include', DOMAIN_NAME); | ||
// Assert name field label is visible | ||
cy.getFormLabelByInputId('name') | ||
.should('be.visible') | ||
.and('contain.text', NAME_FIELD_LABEL); | ||
// Assert name field is visible and enabled | ||
cy.getFormInputFieldById('name').should('be.visible').and('be.enabled'); | ||
// Assert description field label is visible | ||
cy.getFormLabelByInputId('description') | ||
.should('be.visible') | ||
.and('contain.text', DESCRIPTION_FIELD_LABEL); | ||
// Assert description field is visible and enabled | ||
cy.getFormInputFieldById('description') | ||
.should('be.visible') | ||
.and('be.enabled'); | ||
// Assert cancel button is visible and enabled | ||
cy.getFormFooterButtonByType(CANCEL_BUTTON_TEXT) | ||
.should('be.visible') | ||
.and('be.enabled'); | ||
// Assert add/save button is visible and disabled | ||
cy.getFormFooterButtonByType( | ||
isEditForm ? SAVE_BUTTON_TEXT : ADD_BUTTON_TEXT, | ||
'submit' | ||
) | ||
.should('be.visible') | ||
.and('be.disabled'); | ||
if (isEditForm) { | ||
// Assert reset button is visible and disabled | ||
cy.getFormFooterButtonByType(RESET_BUTTON_TEXT) | ||
.should('be.visible') | ||
.and('be.disabled'); | ||
} | ||
} | ||
|
||
function createNamespaceAndOpenEditForm() { | ||
/* TODO: DATA_SETUP - Use API for namespace setup, excluding the test meant to validate functionality via UI */ | ||
// Navigating to the Add Namespace form | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_NAMESPACE); | ||
// Adding a new namespace | ||
addDomainOrNamespace({ nameFieldValue: NAMESPACE_NAME }); | ||
// Selecting the created namespace from the accordion list items | ||
selectAccordionElement(NAMESPACE_NAME); | ||
// Opening the edit form | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_EDIT_NAMESPACE); | ||
} | ||
|
||
function extractDomainIdAndTokenFromResponse(interception) { | ||
const rawTreeObject = interception?.response?.body?.reloadTrees?.ae_tree; | ||
if (rawTreeObject) { | ||
const rawTreeParsed = JSON.parse(rawTreeObject); | ||
rawTreeParsed.every((treeObject) => { | ||
// Exit iteration once id is extracted from nodes array | ||
return treeObject?.nodes?.every((nodeObject) => { | ||
if (nodeObject?.text === DOMAIN_NAME) { | ||
const domainId = nodeObject?.key?.split('-')?.[1]; | ||
const csrfToken = interception?.request?.headers?.['x-csrf-token']; | ||
const idAndToken = { | ||
domainId, | ||
csrfToken, | ||
}; | ||
// Creating an aliased state to store id and token | ||
cy.wrap(idAndToken).as('idAndToken'); | ||
|
||
// Stop iterating once the domain id is found | ||
return false; | ||
} | ||
// Continue iterating | ||
return true; | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
describe('Automate operations on Namespaces: Automation -> Embedded Automate -> Explorer -> {Any-created-domain} -> Namespace form', () => { | ||
beforeEach(() => { | ||
cy.login(); | ||
// Navigate to Explorer under Automation -> Embedded Automate | ||
cy.menu( | ||
AUTOMATION_MENU_OPTION, | ||
EMBEDDED_AUTOMATION_MENU_OPTION, | ||
EXPLORER_MENU_OPTION | ||
); | ||
// Expand "Datastore" accordion if not already expanded | ||
cy.accordion(DATA_STORE_ACCORDION_LABEL); | ||
/* TODO: DATA_SETUP - Refactor to use API for domain data setup */ | ||
// Navigating to the Add Domain form | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_DOMAIN); | ||
// Creating a domain to validate namespace operations | ||
addDomainOrNamespace({ | ||
nameFieldValue: DOMAIN_NAME, | ||
afterDomainOrNamespaceCreation: extractDomainIdAndTokenFromResponse, | ||
}); | ||
cy.expect_flash(flashClassMap.success, FLASH_MESSAGE_ADD_SUCCESS); | ||
// Selecting the created domain from the accordion list items | ||
selectAccordionElement(DOMAIN_NAME); | ||
}); | ||
|
||
it('Validate Add Namespace form fields', () => { | ||
// Navigating to the Add Namespace form | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_NAMESPACE); | ||
|
||
// Validating the form fields | ||
validateNamespaceFormFields(); | ||
|
||
// Cancelling the form | ||
cy.getFormFooterButtonByType(CANCEL_BUTTON_TEXT).click(); | ||
}); | ||
|
||
it('Validate Cancel button', () => { | ||
// Navigating to the Add Namespace form | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_NAMESPACE); | ||
|
||
// Cancelling the form | ||
cy.getFormFooterButtonByType(CANCEL_BUTTON_TEXT) | ||
.should('be.enabled') | ||
.click(); | ||
cy.expect_flash(flashClassMap.warning, FLASH_MESSAGE_CANCELLED); | ||
}); | ||
|
||
it('Validate Name field allows only alphanumeric and _ . - $ characters', () => { | ||
// Navigating to the Add Namespace form | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_NAMESPACE); | ||
// Trying to add a namespace with invalid characters | ||
addDomainOrNamespace({ nameFieldValue: INVALID_NAMESPACE_NAME }); | ||
cy.expect_flash(flashClassMap.error, FLASH_MESSAGE_INVALID_NAMESPACE); | ||
|
||
// Cancelling the form | ||
cy.getFormFooterButtonByType(CANCEL_BUTTON_TEXT).click(); | ||
}); | ||
|
||
it('Validate Edit Namespace form fields', () => { | ||
// Create a namespace and open the edit form | ||
createNamespaceAndOpenEditForm(); | ||
|
||
// Validating the form fields | ||
validateNamespaceFormFields(true); | ||
|
||
// Cancelling the form | ||
cy.getFormFooterButtonByType(CANCEL_BUTTON_TEXT).click(); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note, I'm fine with doing multiple validations in the same test function. We have to be aware these are more integration/component tests so they need not be small unit tests. See also: https://docs.cypress.io/app/core-concepts/best-practices#Creating-Tiny-Tests-With-A-Single-Assertion If the setup is much different, it might be better to keep them seperate like they are here. |
||
|
||
it('Checking whether add, edit & delete namespace works', () => { | ||
// Navigating to the Add Namespace form | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_NAMESPACE); | ||
// Adding a new namespace | ||
addDomainOrNamespace({ nameFieldValue: NAMESPACE_NAME }); | ||
cy.expect_flash(flashClassMap.success, FLASH_MESSAGE_ADD_SUCCESS); | ||
|
||
// Selecting the created namespace from the accordion list items | ||
selectAccordionElement(NAMESPACE_NAME); | ||
// Editing the namespace | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_EDIT_NAMESPACE); | ||
// Checking if the Save button is disabled initially | ||
cy.getFormFooterButtonByType(SAVE_BUTTON_TEXT, 'submit').should( | ||
'be.disabled' | ||
); | ||
cy.getFormInputFieldById('description').clear().type(EDITED_DESCRIPTION); | ||
cy.getFormFooterButtonByType(SAVE_BUTTON_TEXT, 'submit') | ||
.should('be.enabled') | ||
.click(); | ||
cy.expect_flash(flashClassMap.success, FLASH_MESSAGE_SAVE_SUCCESS); | ||
|
||
// Deleting the namespace | ||
cy.expect_browser_confirm_with_text({ | ||
confirmTriggerFn: () => | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_REMOVE_NAMESPACE), | ||
containsText: BROWSER_CONFIRM_REMOVE_MESSAGE, | ||
}); | ||
cy.expect_flash(flashClassMap.success, FLASH_MESSAGE_NAMESPACE_REMOVED); | ||
}); | ||
|
||
it('Checking whether creating a duplicate namespace is restricted', () => { | ||
/* TODO: DATA_SETUP - Use API for namespace setup, excluding the test meant to validate functionality via UI */ | ||
// Navigating to the Add Namespace form | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_NAMESPACE); | ||
// Adding a new namespace | ||
addDomainOrNamespace({ nameFieldValue: NAMESPACE_NAME }); | ||
// Navigating to the Add Namespace form again | ||
cy.toolbar(TOOLBAR_CONFIGURATION, TOOLBAR_ADD_NEW_NAMESPACE); | ||
// Trying to add duplicate namespace | ||
addDomainOrNamespace({ nameFieldValue: NAMESPACE_NAME }); | ||
cy.expect_flash(flashClassMap.error, FLASH_MESSAGE_NAME_ALREADY_EXISTS); | ||
|
||
// Cancelling the form | ||
cy.getFormFooterButtonByType(CANCEL_BUTTON_TEXT).click(); | ||
}); | ||
|
||
it('Checking whether Cancel & Reset buttons work fine in the Edit form', () => { | ||
// Create a namespace and open the edit form | ||
createNamespaceAndOpenEditForm(); | ||
|
||
/* Validating Reset button */ | ||
// Checking if the Reset button is disabled initially | ||
cy.getFormFooterButtonByType(RESET_BUTTON_TEXT).should('be.disabled'); | ||
// Editing name and description fields | ||
cy.getFormInputFieldById('name').clear().type(EDITED_NAMESPACE_NAME); | ||
cy.getFormInputFieldById('description').clear().type(EDITED_DESCRIPTION); | ||
// Resetting | ||
cy.getFormFooterButtonByType(RESET_BUTTON_TEXT) | ||
.should('be.enabled') | ||
.click(); | ||
cy.expect_flash(flashClassMap.warning, FLASH_MESSAGE_RESET_NAMESPACE); | ||
// Confirming the edited fields contain the old values after resetting | ||
cy.getFormInputFieldById('name').should('have.value', NAMESPACE_NAME); | ||
cy.getFormInputFieldById('description').should('have.value', DESCRIPTION); | ||
|
||
/* Validating Cancel button */ | ||
cy.getFormFooterButtonByType(CANCEL_BUTTON_TEXT).click(); | ||
cy.expect_flash(flashClassMap.warning, FLASH_MESSAGE_CANCELLED); | ||
}); | ||
|
||
afterEach(() => { | ||
// retrieve the id and token from the aliased state | ||
// to invoke api for deleting the created domain | ||
cy.get('@idAndToken').then((data) => { | ||
const { domainId, csrfToken } = data; | ||
if (domainId && csrfToken) { | ||
cy.request({ | ||
method: 'POST', | ||
url: `/miq_ae_class/x_button/${domainId}?pressed=miq_ae_domain_delete`, | ||
headers: { | ||
'X-CSRF-Token': csrfToken, | ||
}, | ||
}).then((response) => { | ||
expect(response.status).to.eq(200); | ||
}); | ||
} | ||
}); | ||
}); | ||
}); |
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.
It looks like this is for extracting the csrf token for the deletes in the afterEach below I'm ok with this for now but I do wonder if we need a generic way to do this through the actual API in the cypress tests. Thoughts on this @GilbertCherrie @elsamaryv @Fryguy
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.
Thoughts on this @GilbertCherrie @elsamaryv @Fryguy? @asirvadAbrahamVarghese is this PR ready to go? It was failing in schedule form. I'll close and open the PR so it's tested against the latest merge point.
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.
I’d like to drop this change for now, which uses
cy.request
with a CSRF token to delete data, but I’m unsure if we have a viable alternative. Not sure if we can hit our current endpoints without the token for create/delete operations. For creation, we’d also need to handle the 'Edit aborted: multiple tabs or windows' error if we reuse the same session.For now, I’m thinking we drop this and just stick with the UI-based cleanup, What do you all think?