diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index e621b6883a..e3be30de1f 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - containers: [1, 2, 3] + containers: [1, 2, 3, 4] steps: - uses: actions/checkout@v4 @@ -61,6 +61,8 @@ jobs: CYPRESS_DYNAMIC_SCAN_SYSTEM_IPA_FILE_ID: ${{ vars.CYPRESS_DYNAMIC_SCAN_SYSTEM_IPA_FILE_ID }} CYPRESS_IGNORE_VULNERABILITY_TEST_PACKAGE_NAME: ${{ vars.CYPRESS_IGNORE_VULNERABILITY_TEST_PACKAGE_NAME }} CYPRESS_API_HOST: ${{ vars.CYPRESS_API_HOST }} + SERVICE_ACCOUNT_DUPLICATE_ACCOUNT_ID: ${{ vars.SERVICE_ACCOUNT_DUPLICATE_ACCOUNT_ID }} + SERVICE_ACCOUNT_VIEW_ACCOUNT_ID: ${{ vars.SERVICE_ACCOUNT_VIEW_ACCOUNT_ID }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: start: npm run startprod diff --git a/app/components/ak-checkbox-tree/index.hbs b/app/components/ak-checkbox-tree/index.hbs index 5bf900c2f7..121bc8565e 100644 --- a/app/components/ak-checkbox-tree/index.hbs +++ b/app/components/ak-checkbox-tree/index.hbs @@ -29,6 +29,7 @@ {{style paddingLeft=(this.calculatePadding node.treeDepth)}} data-test-ak-checkbox-tree-node data-test-ak-checkbox-tree-nodeKey='{{node.key}}' + data-test-cy='checkbox-tree-nodeKey-{{node.key}}' > {{#if node.showCheckbox}} diff --git a/app/components/organization/service-account/add-project/list/index.hbs b/app/components/organization/service-account/add-project/list/index.hbs index 1e1d3cf934..fb4516ea8f 100644 --- a/app/components/organization/service-account/add-project/list/index.hbs +++ b/app/components/organization/service-account/add-project/list/index.hbs @@ -57,6 +57,7 @@ - + {{#if r.columnValue.component}} {{#let (component r.columnValue.component) as |Component|}} @@ -120,6 +125,7 @@ diff --git a/app/components/organization/service-account/create/index.hbs b/app/components/organization/service-account/create/index.hbs index bc7371086a..64f87cb31f 100644 --- a/app/components/organization/service-account/create/index.hbs +++ b/app/components/organization/service-account/create/index.hbs @@ -50,6 +50,7 @@ diff --git a/app/components/organization/service-account/details/index.hbs b/app/components/organization/service-account/details/index.hbs index a093c7dccd..8c54b8e0e2 100644 --- a/app/components/organization/service-account/details/index.hbs +++ b/app/components/organization/service-account/details/index.hbs @@ -9,6 +9,7 @@ @route={{item.route}} @linkTitle={{item.linkTitle}} data-test-serviceAccountDetails-breadcrumbItem='{{item.linkTitle}}' + data-test-cy='serviceAccountDetails-breadcrumbItem-{{item.linkTitle}}' /> {{/each}} @@ -39,6 +40,7 @@ diff --git a/app/components/organization/service-account/list/action/index.hbs b/app/components/organization/service-account/list/action/index.hbs index 690645b0fa..7bdaf46ac8 100644 --- a/app/components/organization/service-account/list/action/index.hbs +++ b/app/components/organization/service-account/list/action/index.hbs @@ -1,6 +1,7 @@ @@ -13,6 +14,7 @@ diff --git a/app/components/organization/service-account/list/index.hbs b/app/components/organization/service-account/list/index.hbs index 0cf7f37b95..4431af919f 100644 --- a/app/components/organization/service-account/list/index.hbs +++ b/app/components/organization/service-account/list/index.hbs @@ -36,7 +36,10 @@ @route='authenticated.dashboard.service-account-create' @query={{hash duplicate=null}} > - + {{t 'create'}} @@ -64,14 +67,18 @@ {{else}}
- + - + {{#if r.columnValue.component}} {{#let (component r.columnValue.component) as |Component|}} diff --git a/app/components/organization/service-account/section/access-token/index.hbs b/app/components/organization/service-account/section/access-token/index.hbs index f33b16d6b0..f11f0b136c 100644 --- a/app/components/organization/service-account/section/access-token/index.hbs +++ b/app/components/organization/service-account/section/access-token/index.hbs @@ -38,6 +38,7 @@
@@ -158,6 +160,7 @@ > {{else}} - + {{if this.doesNotExpire (t 'noExpiry') this.expiresOn}} {{/if}} diff --git a/app/components/organization/service-account/section/account-overview/index.hbs b/app/components/organization/service-account/section/account-overview/index.hbs index a995fb8ab0..0d1f53544a 100644 --- a/app/components/organization/service-account/section/account-overview/index.hbs +++ b/app/components/organization/service-account/section/account-overview/index.hbs @@ -15,6 +15,7 @@ class={{ha.classes.headerActionBtn}} {{on 'click' this.handleShowEditView}} data-test-serviceAccountSection-accountOverview-actionBtn + data-test-cy='serviceAccountSection-accountOverview-actionBtn' > @@ -25,6 +26,7 @@ {{#if this.isEditOrCreateView}} {{@serviceAccount.name}} @@ -95,6 +101,7 @@ {{@serviceAccount.description}} @@ -105,6 +112,7 @@ <:footerAction> diff --git a/app/components/organization/service-account/section/select-project/index.hbs b/app/components/organization/service-account/section/select-project/index.hbs index 222f7c865b..5d8a0a9bc2 100644 --- a/app/components/organization/service-account/section/select-project/index.hbs +++ b/app/components/organization/service-account/section/select-project/index.hbs @@ -16,6 +16,7 @@ > diff --git a/app/components/organization/service-account/section/select-project/list/index.hbs b/app/components/organization/service-account/section/select-project/list/index.hbs index 91b5e53a1b..761a757b64 100644 --- a/app/components/organization/service-account/section/select-project/list/index.hbs +++ b/app/components/organization/service-account/section/select-project/list/index.hbs @@ -1,5 +1,6 @@
@@ -35,6 +36,7 @@ diff --git a/app/components/organization/service-account/section/select-project/list/overview/index.hbs b/app/components/organization/service-account/section/select-project/list/overview/index.hbs index 52d659e77b..c70f99530c 100644 --- a/app/components/organization/service-account/section/select-project/list/overview/index.hbs +++ b/app/components/organization/service-account/section/select-project/list/overview/index.hbs @@ -12,6 +12,7 @@ diff --git a/app/components/organization/service-account/section/select-scope/index.hbs b/app/components/organization/service-account/section/select-scope/index.hbs index 83db934b3e..f022e15e86 100644 --- a/app/components/organization/service-account/section/select-scope/index.hbs +++ b/app/components/organization/service-account/section/select-scope/index.hbs @@ -12,6 +12,7 @@ > {{else}} diff --git a/cypress/support/Actions/common/ServiceAccountActions.ts b/cypress/support/Actions/common/ServiceAccountActions.ts new file mode 100644 index 0000000000..fe6b445cea --- /dev/null +++ b/cypress/support/Actions/common/ServiceAccountActions.ts @@ -0,0 +1,111 @@ +import cyTranslate from '../../translations'; + +export default class ServiceAccountActions { + /** + * Delete service account which was created to test + */ + deleteServiceAccount() { + //click more button in service account page + cy.findByTestId('serviceAccountDetails-moreOptionsBtn') + .as('ViewSAMoreButton') + .should('exist'); + + cy.get('@ViewSAMoreButton').click(); + + //click delete option in view more + cy.findByText(cyTranslate('delete')).should('exist').click(); + + //confirm delete in drawer + cy.findByTestId('serviceAccount-confirmDrawer-confirmBtn') + .as('SADeleteButton') + .should('exist'); + + cy.get('@SADeleteButton').click(); + } + + /** + * Edit name in service account + * @param text - string - text that needs to be entered + * @param clear - boolean - should we clear before entering text + * @param value - string - what is the value of input after enter of text + */ + editNameInput(text: string, clear: boolean, value?: string) { + cy.findByTestId('serviceAccountSection-accountOverview-nameInput') + .as('SANameInput') + .should('not.have.attr', 'disabled') + .then(() => { + if (clear) { + cy.get('@SANameInput').clear(); + } + }) + .then(() => { + cy.get('@SANameInput').type(text, { delay: 0 }); + }) + .then(($input) => { + expect($input.val()).to.equal(value ?? text); + }); + } + + /** + * Selects a type of project access by particular service account + * @param type - string - For all or some are the two types + */ + selectProjectType(type: string) { + cy.findByTestId('serviceAccountSection-selectProject-projectAccessSelect') + .find('[data-test-power-select]') + .click(); + + cy.findByText(type).should('exist').click(); + } + + /** + * Saves serivce account + */ + saveServiceAccount() { + cy.findByTestId('serviceAccountCreate-saveButton') + .as('SaveSAButton') + .should('exist'); + + cy.get('@SaveSAButton').click(); + } + + /** + * Asserts the type of project by checking the value of project list container + * @param value - string + */ + assertProjectListContainerState(value: string) { + cy.findByTestId('serviceAccountSection-selectProjectList-container').should( + value + ); + } + + /** + * Clicks edit overview button + */ + clickEditOverviewButton() { + cy.findByTestId('serviceAccountSection-accountOverview-actionBtn') + .as('EditOverviewButton') + .should('exist'); + + cy.get('@EditOverviewButton').click(); + } + + /** + * Clicks update overview button + */ + clickUpdateOverviewButton() { + cy.findByTestId('serviceAccountSection-accountOverview-updateBtn') + .as('UpdateOverviewButton') + .should('exist'); + + cy.get('@UpdateOverviewButton').click(); + } + + /** + * Check Success Message after create, update, delete of service account + * @param msg - string - Message shown for success in toast + */ + checkSuccessMessage(msg: string) { + cy.findByText(msg).should('exist'); + } +} diff --git a/cypress/support/api.routes.ts b/cypress/support/api.routes.ts index 1638a6ba1b..79a0952b16 100644 --- a/cypress/support/api.routes.ts +++ b/cypress/support/api.routes.ts @@ -57,6 +57,10 @@ export const API_ROUTES = { route: '/api/profiles/*/vulnerability_preferences', alias: 'vulnerabilityPreferenceList', }, + serviceAccountList: { + route: '/api/service_accounts*', + alias: 'serviceAccountList', + }, // Single Record routes file: { route: '/api/v2/files', alias: 'file' }, @@ -85,4 +89,8 @@ export const API_ROUTES = { route: '/api/files/*/vulnerability_preferences/*/risk', alias: 'editAnalysisRisk', }, + serviceAccount: { + route: '/api/service_accounts/*', + alias: 'serviceAccount', + }, } as const; diff --git a/cypress/support/application.routes.ts b/cypress/support/application.routes.ts index 5b20324b08..be5a2d7e22 100644 --- a/cypress/support/application.routes.ts +++ b/cypress/support/application.routes.ts @@ -3,5 +3,6 @@ export const APPLICATION_ROUTES = { projects: '/projects', file: '/dashboard/file', sbom: '/dashboard/sbom/apps', + serviceAccount: '/dashboard/organization/settings/service-account', register: '/register', } as const; diff --git a/cypress/tests/service-account.spec.ts b/cypress/tests/service-account.spec.ts new file mode 100644 index 0000000000..7a7df16a34 --- /dev/null +++ b/cypress/tests/service-account.spec.ts @@ -0,0 +1,487 @@ +import cyTranslate from '../support/translations'; + +import LoginActions from '../support/Actions/auth/LoginActions'; +import NetworkActions from '../support/Actions/common/NetworkActions'; +import ServiceAccountActions from '../support/Actions/common/ServiceAccountActions'; + +import { API_ROUTES } from '../support/api.routes'; +import { APPLICATION_ROUTES } from '../support/application.routes'; + +import dayjs from 'dayjs'; + +// Grouped test Actions +const loginActions = new LoginActions(); +const networkActions = new NetworkActions(); +const serviceAccountActions = new ServiceAccountActions(); + +// User credentials +const username = Cypress.env('TEST_USERNAME'); +const password = Cypress.env('TEST_PASSWORD'); +const API_HOST = Cypress.env('API_HOST'); + +const NETWORK_WAIT_OPTS = { + timeout: 60000, +}; + +// Id's to test +const duplicateId = + Cypress.env('SERVICE_ACCOUNT_DUPLICATE_ACCOUNT_ID') || '141'; +const viewId = Cypress.env('SERVICE_ACCOUNT_VIEW_ACCOUNT_ID') || '132'; + +// Creation types and their details +const SERVICE_ACCOUNT_CREATION_TYPE_DETAILS = [ + { + type: 'with expiry', + name: 'Test with expiry', + description: 'Test description with expiry', + expiry: true, + allProjects: true, + expiryDays: '25', + projectName: '', + }, + { + type: 'without expiry', + name: 'Test without expiry', + description: 'Test description without expiry', + expiry: false, + allProjects: true, + expiryDays: '', + projectName: '', + }, + { + type: 'with expiry for some projects', + name: 'Test with expiry for some projects', + description: 'Test description with expiry for some projects', + expiry: true, + allProjects: false, + expiryDays: '5', + projectName: 'com.appknox.mfva', + }, +]; + +// fixes cross origin errors +Cypress.on('uncaught:exception', () => { + // returning false here prevents Cypress from failing the test + return false; +}); + +describe('Service Account', () => { + beforeEach(() => { + networkActions.hideNetworkLogsFor({ ...API_ROUTES.websockets }); + + // Used for clean up in afterEach + cy.wrap(false).as('testCompleted'); + + //Change when service account is created + cy.wrap(null).as('serviceAccountId'); + + // Login or restore session + loginActions.loginWithCredAndSaveSession({ username, password }); + + // Network interceptions + cy.intercept(API_ROUTES.serviceAccountList.route).as('serviceAccountList'); + cy.intercept(API_ROUTES.projectList.route).as('orgProjectList'); + + // Intercept the DELETE request for service accounts + cy.intercept('DELETE', API_ROUTES.serviceAccount.route).as( + 'serviceAccountDelete' + ); + + // Intercept the PUT request for service accounts + cy.intercept('PUT', API_ROUTES.serviceAccount.route).as( + 'serviceAccountUpdate' + ); + + // Intercept the POST request for service accounts + cy.intercept('POST', API_ROUTES.serviceAccountList.route).as( + 'serviceAccountCreate' + ); + + //visit Service Account + cy.visit(APPLICATION_ROUTES.serviceAccount); + + // Necessary API call before showing elements + cy.wait('@serviceAccountList', NETWORK_WAIT_OPTS); + }); + + SERVICE_ACCOUNT_CREATION_TYPE_DETAILS.forEach((app) => + it(`should create service account ${app.type} & delete it`, function () { + //click create service account button + cy.findByTestId('serviceAccountList-createBtn') + .as('CreateSAButton') + .should('exist'); + + cy.get('@CreateSAButton').click(); + + //check if create page is open + cy.findByText(cyTranslate('serviceAccountModule.createTitle')).should( + 'exist' + ); + + //fill in name for service account + serviceAccountActions.editNameInput(app.name, false); + + //fill in description for service account + cy.findByTestId('serviceAccountSection-accountOverview-descriptionInput') + .as('SADescriptionInput') + .should('not.have.attr', 'disabled'); + + cy.get('@SADescriptionInput') + .type(app.description, { delay: 0 }) + .then(($input) => { + expect($input.val()).to.equal(app.description); + }); + + //fill in expiry date for service account + if (app.expiry) { + cy.findByTestId('serviceAccountSection-accessToken-expiryInDaysInput') + .as('SAExpriyInput') + .should('not.have.attr', 'disabled'); + + cy.get('@SAExpriyInput') + .clear() //This is to clear input default value goes to 1 + .clear() //This is to clear 1 and make input empty + .type(app.expiryDays, { delay: 0 }) + .then(($input) => { + expect($input.val()).to.equal(app.expiryDays); + }); + } else { + //click does not expire + cy.findByTestId( + 'serviceAccountSection-accessToken-doesNotExpireCheckbox' + ) + .as('SAExpiryCheckbox') + .should('not.have.attr', 'disabled'); + + cy.get('@SAExpiryCheckbox').check(); + } + + //when only specific project need to be selected + if (!app.allProjects) { + //click on dropdown to select specific project + serviceAccountActions.selectProjectType( + cyTranslate('serviceAccountModule.forSpecificProjects') + ); + + //click on add project for service account + cy.findByTestId( + 'serviceAccountSection-selectProjectList-emptyAddProjectBtn' + ) + .should('exist') + .click(); + + cy.findByTestId('serviceAccountAddProjectList-addBtn') + .as('SAProjectAddButton') + .should('exist'); + + //add button should be disable when no project is selected + cy.get('@SAProjectAddButton').should('have.attr', 'disabled'); + + //search of particular project + cy.findByPlaceholderText(cyTranslate('searchProject')).type( + app.projectName + ); + + //wait fot search result to appear + cy.wait('@orgProjectList', NETWORK_WAIT_OPTS); + + cy.findByTestId('serviceAccountAddProjectList-thead').should('exist'); + + //check particular project + cy.findByTestId('serviceAccountAddProjectList-row') + .find('input[type="checkbox"]') + .as('SAProjectListChecbox') + .check(); + + cy.get('@SAProjectListChecbox').check(); + + //add project in service account + cy.get('@SAProjectAddButton').click(); + } + + serviceAccountActions.saveServiceAccount(); + + //wait for service account to create + cy.wait('@serviceAccountCreate', NETWORK_WAIT_OPTS) + .its('response.body.id') + .then((id: number) => { + cy.wrap(id).as('serviceAccountId'); + }); + + //success message should be appeared + serviceAccountActions.checkSuccessMessage( + cyTranslate('serviceAccountModule.createSuccessMsg') + ); + + //it should land on view project page + cy.findByText(cyTranslate('serviceAccountModule.detailsTitle')).should( + 'exist' + ); + + //name should match + cy.findByTestId('serviceAccountSection-accountOverview-nameValue') + .invoke('text') + .then((text) => text.trim()) + .should('equal', app.name); + + //description should match + cy.findByTestId('serviceAccountSection-accountOverview-descriptionValue') + .invoke('text') + .then((text) => text.trim()) + .should('equal', app.description); + + //copy secret key button should exist initially + cy.findByTestId( + 'serviceAccountSection-accessToken-secretKeyCopyBtn' + ).should('exist'); + + //expiry value should be particular date or no expiry should be shown + cy.findByTestId('serviceaccountsection-accesstoken-expiryvalue') + .invoke('text') + .then((text) => text.trim()) + .as('SAExpiryValue'); + + if (app.expiry) { + const today = dayjs(); + + const expectedDate = today + .add(parseInt(app.expiryDays), 'day') + .format('MMM DD, YYYY'); + + cy.get('@SAExpiryValue').should('equal', expectedDate); + } else { + cy.get('@SAExpiryValue').should('equal', cyTranslate('noExpiry')); + } + + if (app.allProjects) { + //select project list should not exist when all project are selected + serviceAccountActions.assertProjectListContainerState('not.exist'); + } else { + //select project list should exist when specific project are selected + serviceAccountActions.assertProjectListContainerState('exist'); + + //particular project should be shown + cy.findByTestId('serviceAccountSection-selectProjectList-projectName') + .invoke('text') + .then((text) => text.trim()) + .should('equal', app.projectName); + } + + //delete service account + serviceAccountActions.deleteServiceAccount(); + + //wait for delete of service account + cy.wait('@serviceAccountDelete', NETWORK_WAIT_OPTS); + + //success delete message should be appeared + serviceAccountActions.checkSuccessMessage( + cyTranslate('serviceAccountModule.deleteSuccessMsg') + ); + + //change when all scenarios are completed + cy.wrap(true).as('testCompleted'); + }) + ); + + it(`should create duplicate and edit`, function () { + //click view more option button on table + cy.findByTestId(`serviceAccountList-row-${duplicateId}`).within(() => { + cy.findByTestId('serviceAccountList-moreOptionBtn').click(); + }); + + //click on duplicate option + cy.findByText('Duplicate').should('exist').click(); + + //fill in name for duplicate account + serviceAccountActions.editNameInput( + ' Duplicate', + false, + 'Cypress Test Duplicate' + ); + + //click on save service account button + serviceAccountActions.saveServiceAccount(); + + //wait for service account to get create + cy.wait('@serviceAccountCreate', NETWORK_WAIT_OPTS) + .its('response.body.id') + .then((id: number) => { + cy.wrap(id).as('serviceAccountId'); + }); + + //success message should be appeared + serviceAccountActions.checkSuccessMessage( + cyTranslate('serviceAccountModule.createSuccessMsg') + ); + + //click on edit overview button + serviceAccountActions.clickEditOverviewButton(); + + //re-edit name for service account + serviceAccountActions.editNameInput('Try Duplicate', true); + + //click on update overview button + serviceAccountActions.clickUpdateOverviewButton(); + + cy.wait('@serviceAccountUpdate', NETWORK_WAIT_OPTS); + + //success message should be appeared + serviceAccountActions.checkSuccessMessage( + cyTranslate('serviceAccountModule.editSuccessMsg') + ); + + //check selected scope checked and unchecked length + cy.findAllByTestId( + 'serviceAccountSection-selectScope-nodeLabelIcon-checked' + ).should('have.length', 1); + + cy.findAllByTestId( + 'serviceAccountSection-selectScope-nodeLabelIcon-unchecked' + ).should('have.length', 3); + + //click on edit scope button + cy.findByTestId('serviceAccountSection-selectScope-actionBtn') + .as('EditScopeButton') + .should('exist'); + + cy.get('@EditScopeButton').click(); + + //click on parent checkbox to select all + cy.findByTestId('checkbox-tree-nodeKey-public-api').within(() => { + cy.get('[data-test-checkbox]').click(); + }); + + //click on update select scope button + cy.findByTestId('serviceAccountSection-selectScope-updateBtn') + .as('UpdateScopeButton') + .should('exist'); + + cy.get('@UpdateScopeButton').click(); + + cy.wait('@serviceAccountUpdate', NETWORK_WAIT_OPTS); + + //success message should be appeared + serviceAccountActions.checkSuccessMessage( + cyTranslate('serviceAccountModule.editSuccessMsg') + ); + + //check if all scope are checked + cy.findAllByTestId( + 'serviceAccountSection-selectScope-nodeLabelIcon-checked' + ).should('have.length', 4); + + //no uncheck should be present + cy.findAllByTestId( + 'serviceAccountSection-selectScope-nodeLabelIcon-unchecked' + ).should('not.exist'); + + //select project list should exist when specific project are selected + serviceAccountActions.assertProjectListContainerState('exist'); + + //clcik on edit project button + cy.findByTestId('serviceAccountSection-selectProject-actionBtn') + .as('EditProjectButton') + .should('exist'); + + cy.get('@EditProjectButton').click(); + + //click on dropdown to select all project + serviceAccountActions.selectProjectType(cyTranslate('allProjects')); + + //click on update project button + cy.findByTestId('serviceAccountSection-selectProject-updateBtn') + .as('UpdateProjectButton') + .should('exist'); + + cy.get('@UpdateProjectButton').click(); + + cy.wait('@serviceAccountUpdate', NETWORK_WAIT_OPTS); + + //success message should be appeared + serviceAccountActions.checkSuccessMessage( + cyTranslate('serviceAccountModule.editSuccessMsg') + ); + + //select project list should not exist when all project are selected + serviceAccountActions.assertProjectListContainerState('not.exist'); + + //delete service account + serviceAccountActions.deleteServiceAccount(); + + cy.wait('@serviceAccountDelete', NETWORK_WAIT_OPTS); + + //success delete message should be appeared + serviceAccountActions.checkSuccessMessage( + cyTranslate('serviceAccountModule.deleteSuccessMsg') + ); + + //change when all scenarios are completed + cy.wrap(true).as('testCompleted'); + }); + + it(`should check table, view and edit description`, function () { + //check position of particular service accoun + cy.findByTestId('serviceAccountList-table') + .find('tr') + .last() + .should('have.attr', 'data-test-cy', `serviceAccountList-row-${viewId}`); + + //click on view service account button from table + cy.findByTestId(`serviceAccountList-row-${viewId}`).within(() => { + cy.findByTestId('serviceAccountList-viewDetailsLink').click(); + }); + + //click on edit overview button + serviceAccountActions.clickEditOverviewButton(); + + //fill in description for service account + cy.findByTestId('serviceAccountSection-accountOverview-descriptionInput') + .as('SADescriptionInput') + .should('not.have.attr', 'disabled'); + + cy.get('@SADescriptionInput').clear().type(dayjs().format(), { delay: 0 }); + + //click on update overview button + serviceAccountActions.clickUpdateOverviewButton(); + + cy.wait('@serviceAccountUpdate', NETWORK_WAIT_OPTS); + + //success message should be appeared + serviceAccountActions.checkSuccessMessage( + cyTranslate('serviceAccountModule.editSuccessMsg') + ); + + //click on service account breadcrumb to go back + cy.findByTestId('serviceAccountDetails-breadcrumbItem-Service Account') + .as('ServiceAccountBreadcrumb') + .should('exist'); + + cy.get('@ServiceAccountBreadcrumb').click(); + + //position of particular service account should not change after edit + cy.findByTestId('serviceAccountList-table') + .find('tr') + .last() + .should('have.attr', 'data-test-cy', `serviceAccountList-row-${viewId}`); + + cy.wrap(true).as('testCompleted'); + }); + + afterEach(() => { + // Delete service account if test fails + cy.get('@testCompleted').then((testCompleted) => { + if (!testCompleted) { + cy.getAliases(['@serviceAccountId']).then(([id]) => { + if (id) { + //only of account is created + cy.makeAuthenticatedAPIRequest({ + method: 'DELETE', + url: `${API_HOST}/api/service_accounts/${id}/`, + body: { all: true }, + }); + } + }); + } + }); + }); +});