diff --git a/cypress/e2e/awx/administration/containerGroups.cy.ts b/cypress/e2e/awx/administration/containerGroups.cy.ts deleted file mode 100644 index 11b3699046..0000000000 --- a/cypress/e2e/awx/administration/containerGroups.cy.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { InstanceGroup } from '../../../../frontend/awx/interfaces/InstanceGroup'; -//Currently there is no separate interface that exists for Container Groups- there is only one for Instance Groups. -//Need to confirm whether we use the interface for Instance Groups as the interface for a Container Group. - -describe('Container Groups', () => { - let instanceGroup: InstanceGroup; - - before(() => { - cy.awxLogin(); - }); - - beforeEach(() => { - cy.createAwxInstanceGroup().then((ig: InstanceGroup) => { - instanceGroup = ig; - }); - }); - - afterEach(() => { - cy.deleteAwxInstanceGroup(instanceGroup, { failOnStatusCode: false }); - }); - - describe('Container Groups: List View', () => { - it.skip('can create new Container Group, assert info on details page, then delete the container group', () => { - //Assert navigation to Container Group list - //Assert redirect to details page after creations - //Assert info displayed on details page - //Assert deletion of the Container Group - //Delete the Container Group created in this test - }); - - it.skip('can edit Container Group and assert the edited info', () => { - //Utilize the Container Group created in the beforeEach block - //Assert original info of the Container Group - //After edit, assert the edited info of the Container Group - }); - - it.skip('can delete Container Group and assert the deletion', () => { - //Utilize the Container Group created in the beforeEach block - //Assert the deletion of the container group - }); - - it.skip('can bulk delete Container Groups and assert the deletion', () => { - //Utilize the Container Group created in the beforeEach block - //Create 1 or 2 additional Container Groups in this test in order to have multiples to delete - //Assert the deletion of the Container Groups - }); - }); - - describe('Container Groups: Details View', () => { - it.skip('can edit Container Group from the details page and assert edited info', () => { - //Utilize the Container Group created in the beforeEach block - //Assert the navigation to the Edit button on the details page - //Assert the edited info of the IG - }); - - it.skip('can delete Container Group and assert the deletion', () => { - //Utilize the Container Group created in the beforeEach block - //Assert the navigation to the Delete button on the details page - //Assert the deletion - }); - }); - - describe('Container Groups: Jobs Tab', () => { - //Add a beforeEach block and use it to create a job template utilizing the IG created in the original beforeEach block - //Add a command to trigger the launch of the job template - //Add an afterEach block to delete the job template - - it.skip('can visit the Container group -> jobs tab, trigger a job, let the job finish, then view the job on the jobs list tab of the Container group', () => { - //Utilize the Container group created in the beforeEach block - //Assert the navigation to the container group -> jobs tab - //Assert the expected job in the list - }); - - it.skip('can visit the Container group -> jobs tab and relaunch a job associated with that Container group', () => { - //Utilize the Container group created in the beforeEach block - //Assert the navigation to the container group -> jobs tab - //Assert the presence of the relaunch button - //Assert the relaunch trigger - //Assert the cancellation of the job launch - }); - - it.skip('can visit the Container group -> jobs tab and delete a job associated with that Container group', () => { - //Utilize the container group created in the beforeEach block - //Assert the navigation to the container group -> jobs tab - //Assert the presence of the delete button - //Assert the deletion of the job - }); - }); -}); diff --git a/cypress/e2e/awx/administration/instanceGroups.cy.ts b/cypress/e2e/awx/administration/instanceGroups.cy.ts index 26122e1ad6..7c6880842a 100644 --- a/cypress/e2e/awx/administration/instanceGroups.cy.ts +++ b/cypress/e2e/awx/administration/instanceGroups.cy.ts @@ -1,330 +1,911 @@ import { randomString } from '../../../../framework/utils/random-string'; +import { Instance } from '../../../../frontend/awx/interfaces/Instance'; import { InstanceGroup } from '../../../../frontend/awx/interfaces/InstanceGroup'; +import { Inventory } from '../../../../frontend/awx/interfaces/Inventory'; +import { JobTemplate } from '../../../../frontend/awx/interfaces/JobTemplate'; import { Organization } from '../../../../frontend/awx/interfaces/Organization'; -import { Credential } from '../../../../frontend/awx/interfaces/Credential'; -describe('Instance Groups', () => { - let instanceGroup: InstanceGroup; - - before(() => { - cy.awxLogin(); - }); +import { Project } from '../../../../frontend/awx/interfaces/Project'; +import { Team } from '../../../../frontend/awx/interfaces/Team'; +import { AwxUser } from '../../../../frontend/awx/interfaces/User'; +import { awxAPI } from '../../../support/formatApiPathForAwx'; + +const instanceGroupTypes = ['Instance', 'Container']; +instanceGroupTypes.forEach((igType) => { + describe(`${igType} Groups: List view`, () => { + let instanceGroup: InstanceGroup; + const testSignature: string = randomString(5, undefined, { isLowercase: true }); + function generateInstanceGroupName(): string { + return `test-${testSignature}-${igType.toLowerCase()}-group-${randomString(5, undefined, { isLowercase: true })}`; + } + + before(() => { + cy.awxLogin(); + }); - beforeEach(() => { - cy.createAwxInstanceGroup().then((ig: InstanceGroup) => { - instanceGroup = ig; + beforeEach(() => { + cy.createAwxInstanceGroup( + igType === 'Container' + ? { + name: 'E2E Container Group ' + randomString(4), + is_container_group: true, + max_concurrent_jobs: 0, + max_forks: 0, + pod_spec_override: '', + } + : { + name: 'E2E Instance Group ' + randomString(4), + percent_capacity_remaining: 100, + policy_instance_minimum: 0, + } + ).then((ig: InstanceGroup) => { + instanceGroup = ig; + }); }); - }); - afterEach(() => { - cy.deleteAwxInstanceGroup(instanceGroup, { failOnStatusCode: false }); - }); + afterEach(() => { + cy.deleteAwxInstanceGroup(instanceGroup, { failOnStatusCode: false }); + }); - describe('Instance Groups: List View', () => { - it('can create new Instance Group, enable the instance, assert info on details page, then delete the instance group', () => { - const name = 'E2E Instance Group' + randomString(4); - cy.navigateTo('awx', 'instance-groups'); + it(`can create new ${igType} Group, assert info on details page and then delete the ${igType.toLowerCase()} group from list view`, () => { + const name = `E2E ${igType} Group` + randomString(4); cy.clickButton(/^Create group$/); - cy.clickLink(/^Create instance group$/); + cy.clickLink(`Create ${igType.toLowerCase()} group`); cy.get('[data-cy="name"]').type(name); + if (igType === 'Instance') { + cy.get('[data-cy="policy-instance-minimum"]').clear(); + cy.get('[data-cy="policy-instance-minimum"]').type('1'); + cy.get('[data-cy="policy-instance-percentage"]').clear(); + cy.get('[data-cy="policy-instance-percentage"]').type('2%'); + } + cy.get('[data-cy="max-concurrent-jobs"]').clear(); + cy.get('[data-cy="max-concurrent-jobs"]').type('3'); + cy.get('[data-cy="max-forks"]').clear(); + cy.get('[data-cy="max-forks"]').type('4'); - cy.get('[data-cy="policy-instance-minimum"]').clear(); - cy.get('[data-cy="policy-instance-minimum"]').type('1'); + cy.intercept('POST', awxAPI`/instance_groups/`).as('createInstanceGroup'); + cy.clickButton(`Create ${igType} Group`); + cy.wait('@createInstanceGroup') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(201); + }); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes(`infrastructure/instance_groups/${igType.toLowerCase()}-group`)) + .to.be.true; + }); + cy.verifyPageTitle(name); + if (igType === 'Instance') { + cy.getByDataCy('policy-instance-minimum').should('have.text', '1'); + cy.getByDataCy('policy-instance-percentage').should('have.text', '2%'); + } + cy.getByDataCy('max-concurrent-jobs').should('have.text', '3'); + cy.getByDataCy('max-forks').should('have.text', '4'); + + cy.clickPageAction(`delete-${igType.toLowerCase()}-group`); + cy.intercept('DELETE', awxAPI`/instance_groups/*/`).as('deleteInstanceGroup'); + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains(`Permanently delete ${igType.toLowerCase()} groups`); + cy.get('button') + .contains(`Delete ${igType.toLowerCase()} group`) + .should('have.attr', 'aria-disabled', 'true'); + cy.getByDataCy('name-column-cell').should('have.text', name); + cy.get('input[id="confirm"]').click(); + cy.get('button').contains(`Delete ${igType.toLowerCase()} group`).click(); + }); + cy.wait('@deleteInstanceGroup') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); + }); + }); + + it(`can edit ${igType} Group from list view and assert the edited info`, () => { + cy.filterTableBySingleSelect('name', instanceGroup.name); + cy.clickTableRowKebabAction(instanceGroup.name, `edit-${igType.toLowerCase()}-group`, false); + cy.get('[data-cy="name"]').clear(); + cy.get('[data-cy="name"]').type(`${instanceGroup.name}- edited`); - cy.get('[data-cy="policy-instance-percentage"]').clear(); - cy.get('[data-cy="policy-instance-percentage"]').type('2'); + if (igType === 'Instance') { + cy.get('[data-cy="policy-instance-minimum"]').clear(); + cy.get('[data-cy="policy-instance-minimum"]').type('1'); + + cy.get('[data-cy="policy-instance-percentage"]').clear(); + cy.get('[data-cy="policy-instance-percentage"]').type('2'); + } cy.get('[data-cy="max-concurrent-jobs"]').clear(); cy.get('[data-cy="max-concurrent-jobs"]').type('3'); cy.get('[data-cy="max-forks"]').clear(); cy.get('[data-cy="max-forks"]').type('4'); - cy.clickButton(/^Create Instance Group$/); - cy.verifyPageTitle(name); - }); - it('can create a Container Group, assert information on the details page, and then delete the instance group', () => { - const containerGroupName = 'E2E Instance Group' + randomString(4); - const credentialName = 'E2E Credential ' + randomString(4); - let organization: Organization; - - let credential: Credential; - cy.createAwxOrganization().then((org) => { - organization = org; - - cy.createAWXCredential({ - kind: 'kubernetes_bearer_token', - organization: organization.id, - credential_type: 17, - name: credentialName, - }).then((cred) => { - credential = cred; - cy.navigateTo('awx', 'instance-groups'); - cy.clickButton(/^Create group$/); - cy.clickLink(/^Create container group$/); - cy.get('#credential-select').type(credential.name); - cy.get('[data-cy="name"]').type(containerGroupName); - cy.getByDataCy('override').click({ force: true }); - cy.get('[data-cy="max-concurrent-jobs"]').clear(); - cy.get('[data-cy="max-concurrent-jobs"]').type('3'); - - cy.get('[data-cy="max-forks"]').clear(); - cy.get('[data-cy="max-forks"]').type('4'); - cy.clickButton(/^Create Container Group$/); - cy.verifyPageTitle(containerGroupName); + cy.intercept('PATCH', awxAPI`/instance_groups/${instanceGroup.id.toString()}/`).as( + 'editInstanceGroup' + ); + + cy.clickButton(`Save ${igType} Group`); + cy.wait('@editInstanceGroup') + .then((response) => { + expect(response?.response?.statusCode).to.eql(200); + }) + .its('response.body') + .then((response: InstanceGroup) => { + expect(response.name).contains(instanceGroup.name); + expect(response.max_concurrent_jobs.toString()).to.equal('3'); + expect(response.max_forks.toString()).to.equal('4'); + if (igType === 'Instance') { + expect(response?.policy_instance_minimum?.toString()).to.equal('1'); + expect(response?.policy_instance_percentage?.toString()).to.equal('2'); + } }); - }); + cy.verifyPageTitle(`${instanceGroup.name}- edited`); + if (igType === 'Instance') { + cy.getByDataCy('policy-instance-minimum').should('have.text', '1'); + cy.getByDataCy('policy-instance-percentage').should('have.text', '2%'); + } + cy.getByDataCy('max-concurrent-jobs').should('have.text', '3'); + cy.getByDataCy('max-forks').should('have.text', '4'); }); - it('can edit Container Group and assert the edited info', () => { - const containerGroupName = 'E2E Instance Group' + randomString(4); - const credentialName = 'E2E Credential ' + randomString(4); - let organization: Organization; - - let credential: Credential; - cy.createAwxOrganization().then((org) => { - organization = org; - cy.createAWXCredential({ - kind: 'kubernetes_bearer_token', - organization: organization.id, - credential_type: 17, - name: credentialName, - }).then((cred) => { - credential = cred; - cy.createAwxInstanceGroup({ - is_container_group: true, - credential: cred.id, - name: containerGroupName, - }).then(() => { - cy.navigateTo('awx', 'instance-groups'); - cy.filterTableBySingleSelect('name', containerGroupName); - cy.clickTableRowPinnedAction(containerGroupName, 'edit-container-group', false); - cy.get('#credential-select').type(credential.name); - cy.get('[data-cy="name"]').type(containerGroupName); - cy.getByDataCy('override').click({ force: true }); - cy.get('[data-cy="max-concurrent-jobs"]').clear(); - cy.get('[data-cy="max-concurrent-jobs"]').type('3'); - - cy.get('[data-cy="max-forks"]').clear(); - cy.get('[data-cy="max-forks"]').type('4'); - cy.clickButton(/^Save Container Group$/); - cy.verifyPageTitle(containerGroupName); - }); - }); + it(`can bulk delete ${igType} groups from list view and assert the deletion`, () => { + const arrayOfElementText = []; + for (let i = 0; i < 5; i++) { + const instanceGroupName = generateInstanceGroupName(); + cy.createAwxInstanceGroup( + igType === 'Container' + ? { + name: instanceGroupName, + is_container_group: true, + max_concurrent_jobs: 0, + max_forks: 0, + pod_spec_override: '', + } + : { + name: instanceGroupName, + percent_capacity_remaining: 100, + policy_instance_minimum: 100, + } + ); + arrayOfElementText.push(instanceGroupName); + } + cy.filterTableByMultiSelect('name', arrayOfElementText); + cy.get('tbody tr').should('have.length', 5); + cy.getByDataCy('select-all').click(); + cy.clickToolbarKebabAction('delete-selected-instance-groups'); + cy.intercept('DELETE', awxAPI`/instance_groups/*/`).as('deleteInstanceGroup'); + + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains(`Permanently delete ${igType.toLowerCase()} groups`); + cy.get('button') + .contains(`Delete ${igType.toLowerCase()} group`) + .should('have.attr', 'aria-disabled', 'true'); + cy.get('input[id="confirm"]').click(); + cy.get('button').contains(`Delete ${igType.toLowerCase()} group`).click(); }); - }); - it('can edit Container Group from the detail view and assert the edited info', () => { - const containerGroupName = 'E2E Instance Group' + randomString(4); - const credentialName = 'E2E Credential ' + randomString(4); - let organization: Organization; - - let credential: Credential; - cy.createAwxOrganization().then((org) => { - organization = org; - cy.createAWXCredential({ - kind: 'kubernetes_bearer_token', - organization: organization.id, - credential_type: 17, - name: credentialName, - }).then((cred) => { - credential = cred; - cy.createAwxInstanceGroup({ - is_container_group: true, - - name: containerGroupName, - }).then(() => { - cy.navigateTo('awx', 'instance-groups'); - cy.filterTableBySingleSelect('name', containerGroupName); - - cy.clickTableRowLink('name', containerGroupName, { disableFilter: true }); - cy.clickPageAction('edit-container-group'); - - cy.get('#credential-select').type(credential.name); - cy.get('[data-cy="name"]').clear(); - cy.get('[data-cy="name"]').type(`${containerGroupName}-edited`); - cy.getByDataCy('override').click({ force: true }); - cy.get('[data-cy="max-concurrent-jobs"]').clear(); - cy.get('[data-cy="max-concurrent-jobs"]').type('3'); - - cy.get('[data-cy="max-forks"]').clear(); - cy.get('[data-cy="max-forks"]').type('4'); - cy.clickButton(/^Save Container Group$/); - cy.verifyPageTitle(`${containerGroupName}-edited`); - }); + cy.clickModalButton('Close'); + cy.wait('@deleteInstanceGroup') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); }); - }); }); - it('can delete a Container Group from the detail view', () => { - const containerGroupName = 'E2E Instance Group' + randomString(4); - cy.createAwxInstanceGroup({ - is_container_group: true, - name: containerGroupName, - }).then(() => { - cy.navigateTo('awx', 'instance-groups'); - cy.filterTableBySingleSelect('name', containerGroupName); - cy.clickTableRowLink('name', containerGroupName, { disableFilter: true }); - cy.clickPageAction('delete-container-group'); - cy.clickModalConfirmCheckbox(); - cy.clickButton('Delete container group'); - cy.verifyPageTitle('Instance Groups'); + it(`bulk deletion dialog shows warnings for ${igType} groups that cannot be deleted`, () => { + const arrayOfElementText = [instanceGroup.name]; + arrayOfElementText.push(igType === 'Container' ? 'default' : 'controlplane'); + cy.filterTableByMultiSelect('name', arrayOfElementText); + cy.get('tbody tr').should('have.length', 2); + cy.get('#select-all').click(); + cy.clickToolbarKebabAction('delete-selected-instance-groups'); + cy.intercept('DELETE', awxAPI`/instance_groups/*/`).as('deleteInstanceGroup'); + + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.contains( + 'of the selected instance groups cannot be deleted due to insufficient permission.' + ).should('be.visible'); + cy.contains( + 'Deleting instance groups could impact other resources that rely on them.' + ).should('be.visible'); + cy.get('header').contains(`Permanently delete ${igType.toLowerCase()} groups`); + cy.get('button') + .contains(`Delete ${igType.toLowerCase()} group`) + .should('have.attr', 'aria-disabled', 'true'); + cy.get('input[id="confirm"]').click(); + cy.get('button') + .contains(`Delete ${igType.toLowerCase()} group`) + .should('have.attr', 'aria-disabled', 'false') + .click(); }); + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains(`Permanently delete ${igType.toLowerCase()} groups`); + cy.get('[data-cy="name-column-cell"]').should('have.text', instanceGroup.name); + }); + cy.assertModalSuccess(); + cy.clickModalButton('Close'); + cy.wait('@deleteInstanceGroup') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); + }); + }); + }); + + describe(`${igType} Groups: Details Tab`, () => { + let instanceGroup: InstanceGroup; + + before(() => { + cy.awxLogin(); }); - it('can edit Instance Group and assert the edited info', () => { + beforeEach(() => { + cy.createAwxInstanceGroup( + igType === 'Container' + ? { + name: 'E2E Container Group ' + randomString(4), + is_container_group: true, + max_concurrent_jobs: 0, + max_forks: 0, + pod_spec_override: '', + } + : { + name: 'E2E Instance Group ' + randomString(4), + percent_capacity_remaining: 100, + policy_instance_minimum: 0, + } + ).then((ig: InstanceGroup) => { + instanceGroup = ig; + }); cy.navigateTo('awx', 'instance-groups'); + cy.verifyPageTitle('Instance Groups'); + }); + + afterEach(() => { + cy.deleteAwxInstanceGroup(instanceGroup, { failOnStatusCode: false }); + }); + + it(`can edit ${igType} Group from the details page and assert edited info`, () => { cy.filterTableBySingleSelect('name', instanceGroup.name); - cy.clickTableRowKebabAction(instanceGroup.name, 'edit-instance-group', false); + cy.get('[data-cy="name-column-cell"]').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes(`infrastructure/instance_groups/${igType.toLowerCase()}-group`)) + .to.be.true; + }); + cy.getByDataCy(`edit-${igType.toLowerCase()}-group`).click(); cy.get('[data-cy="name"]').clear(); cy.get('[data-cy="name"]').type(`${instanceGroup.name}- edited`); - cy.get('[data-cy="policy-instance-minimum"]').clear(); - cy.get('[data-cy="policy-instance-minimum"]').type('1'); + if (igType === 'Instance') { + cy.get('[data-cy="policy-instance-minimum"]').clear(); + cy.get('[data-cy="policy-instance-minimum"]').type('1'); - cy.get('[data-cy="policy-instance-percentage"]').clear(); - cy.get('[data-cy="policy-instance-percentage"]').type('2'); + cy.get('[data-cy="policy-instance-percentage"]').clear(); + cy.get('[data-cy="policy-instance-percentage"]').type('2'); + } cy.get('[data-cy="max-concurrent-jobs"]').clear(); cy.get('[data-cy="max-concurrent-jobs"]').type('3'); cy.get('[data-cy="max-forks"]').clear(); cy.get('[data-cy="max-forks"]').type('4'); - - cy.clickButton(/^Save Instance Group$/); + cy.intercept('PATCH', awxAPI`/instance_groups/${instanceGroup.id.toString()}/`).as( + 'editInstanceGroup' + ); + + cy.clickButton(`Save ${igType} Group`); + cy.wait('@editInstanceGroup') + .then((response) => { + expect(response?.response?.statusCode).to.eql(200); + }) + .its('response.body') + .then((response: InstanceGroup) => { + expect(response.name).contains(instanceGroup.name); + expect(response.max_concurrent_jobs.toString()).to.equal('3'); + expect(response.max_forks.toString()).to.equal('4'); + if (igType === 'Instance') { + expect(response?.policy_instance_minimum?.toString()).to.equal('1'); + expect(response?.policy_instance_percentage?.toString()).to.equal('2'); + } + }); cy.verifyPageTitle(`${instanceGroup.name}- edited`); + if (igType === 'Instance') { + cy.getByDataCy('policy-instance-minimum').should('have.text', '1'); + cy.getByDataCy('policy-instance-percentage').should('have.text', '2%'); + } + cy.getByDataCy('max-concurrent-jobs').should('have.text', '3'); + cy.getByDataCy('max-forks').should('have.text', '4'); }); - it('can delete Instance Group and assert the deletion', () => { - //Add more robust assertions to this test- specifically with regard to deletion - //Change this test to utilize the IG created in the beforeEach block - cy.createAwxInstanceGroup().then((instanceGroup: InstanceGroup) => { - cy.navigateTo('awx', 'instance-groups'); - cy.filterTableBySingleSelect('name', instanceGroup.name); - cy.clickTableRowKebabAction(instanceGroup.name, 'delete-instance-group', false); - cy.get('#confirm').click(); - cy.clickButton(/^Delete instance group/); - cy.contains(/^Success$/); - cy.clickButton(/^Close$/); - cy.clickButton(/^Clear all filters$/); + it(`can delete ${igType} Group from the details page and assert the deletion`, () => { + cy.filterTableBySingleSelect('name', instanceGroup.name); + cy.get('[data-cy="name-column-cell"]').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes(`infrastructure/instance_groups/${igType.toLowerCase()}-group`)) + .to.be.true; + }); + cy.clickPageAction(`delete-${igType.toLowerCase()}-group`); + cy.intercept('DELETE', awxAPI`/instance_groups/*/`).as('deleteInstanceGroup'); + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains(`Permanently delete ${igType.toLowerCase()} groups`); + cy.get('button') + .contains(`Delete ${igType.toLowerCase()} group`) + .should('have.attr', 'aria-disabled', 'true'); + cy.getByDataCy('name-column-cell').should('have.text', instanceGroup.name); + cy.get('input[id="confirm"]').click(); + cy.get('button').contains(`Delete ${igType.toLowerCase()} group`).click(); }); + cy.wait('@deleteInstanceGroup') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); + }); }); + }); + + describe(`${igType} Groups: Team access Tab`, () => { + let team: Team; + let instanceGroup: InstanceGroup; - it.skip('can bulk delete Instance Groups and assert the deletion', () => { - //Utilize the IG created in the beforeEach block - //Create 1 or 2 additional IGs in this test in order to have multiples to delete - //Assert the deletion of the IGs + before(() => { + cy.awxLogin(); }); - it('bulk deletion dialog shows warnings for instance groups that cannot be deleted', () => { + beforeEach(function () { + cy.createAwxInstanceGroup( + igType === 'Container' + ? { + name: 'E2E Container Group ' + randomString(4), + is_container_group: true, + max_concurrent_jobs: 0, + max_forks: 0, + pod_spec_override: '', + } + : { + name: 'E2E Instance Group ' + randomString(4), + percent_capacity_remaining: 100, + policy_instance_minimum: 0, + } + ).then((ig: InstanceGroup) => { + instanceGroup = ig; + }); + cy.createAwxTeam(this.globalOrganization as Organization).then((createdTeam) => { + team = createdTeam; + }); cy.navigateTo('awx', 'instance-groups'); - cy.get('#select-all').click(); - cy.clickToolbarKebabAction('delete-selected-instance-groups'); - cy.contains( - 'of the selected instance groups cannot be deleted due to insufficient permission.' - ).should('be.visible'); - cy.contains( - 'Deleting instance groups could impact other resources that rely on them.' - ).should('be.visible'); - cy.contains('button', 'Cancel').click(); - cy.get('input[data-cy=select-all]').click(); - //Add final assertion here + cy.verifyPageTitle('Instance Groups'); }); - }); - describe('Instance Groups: Details View', () => { - it.skip('can edit Instance Group from the details page and assert edited info', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the Edit button on the details page - //Assert the edited info of the IG + afterEach(() => { + cy.deleteAwxInstanceGroup(instanceGroup, { failOnStatusCode: false }); + cy.deleteAwxTeam(team, { failOnStatusCode: false }); }); - it.skip('can delete Instance Group and assert the deletion', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the Delete button on the details page - //Assert the deletion + it(`can visit the ${igType} group -> team access tab, add a team, view the team on the teams list and then delete team`, () => { + cy.filterTableBySingleSelect('name', instanceGroup.name); + cy.get('[data-cy="name-column-cell"]').within(() => { + cy.get('a').click(); + }); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes(`infrastructure/instance_groups/${igType.toLowerCase()}-group`)) + .to.be.true; + }); + cy.clickTab(/^Team access$/, true); + cy.get('.pf-v5-c-empty-state__title-text').contains( + /^There are currently no teams assigned to this instance group./ + ); + cy.get('.pf-v5-c-empty-state__body').contains(/^Add a role by clicking the button below./); + cy.getByDataCy('add-roles').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('infrastructure/instance_groups/')).to.be.true; + expect(currentUrl.includes('instance-groups/teams/add-teams')).to.be.true; + }); + cy.get('[data-cy="wizard-nav"] li').eq(0).should('contain.text', 'Select team(s)'); + cy.get('[data-cy="wizard-nav"] li').eq(1).should('contain.text', 'Select roles to apply'); + cy.get('[data-cy="wizard-nav"] li').eq(2).should('contain.text', 'Review'); + cy.get('.pf-v5-c-page__main-body > .pf-v5-c-title').should('have.text', 'Select team(s)'); + cy.filterTableBySingleSelect('name', team.name); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); + cy.get('[data-cy="checkbox-column-cell"] input').click(); + }); + cy.getByDataCy('Submit').click(); + cy.get('.pf-v5-c-page__main-body > .pf-v5-c-title').should( + 'have.text', + 'Select roles to apply' + ); + cy.searchAndDisplayResource('Admin'); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); + cy.get('[data-cy="checkbox-column-cell"] input').click(); + }); + cy.getByDataCy('Submit').click(); + cy.get('.pf-v5-c-page__main-body > .pf-v5-c-title').should('have.text', 'Review'); + cy.get('[data-cy="expandable-section-teams"]') + .should('be.visible') + .within(() => { + cy.get('tbody tr').should('have.length', 1); + cy.get('[data-cy="name-column-cell"]').should('have.text', team.name); + }); + cy.get('[data-cy="expandable-section-awxRoles"]').should('be.visible'); + cy.intercept('POST', awxAPI`/role_team_assignments/`).as('teamAdded'); + cy.getByDataCy('Submit').click(); + cy.wait('@teamAdded') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(201); + }); + cy.visit( + `/infrastructure/instance_groups/${igType.toLowerCase()}-groups/${instanceGroup.id}/team-access` + ); + cy.verifyPageTitle(instanceGroup.name); + cy.get('[data-cy="text-input"]').find('input').type(team.name); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); + cy.get('[data-cy="remove-role"]').click(); + }); + cy.intercept('DELETE', awxAPI`/role_team_assignments/*/`).as('teamRemoved'); + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains('Remove role'); + cy.get('button').contains('Remove role').should('have.attr', 'aria-disabled', 'true'); + cy.getByDataCy('team-name-column-cell').should('have.text', team.name); + cy.get('input[id="confirm"]').click(); + cy.get('button').contains('Remove role').click(); + }); + cy.wait('@teamRemoved') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); + }); }); }); - describe('Instance Groups: Instances Tab', () => { - //Add a before block here to create an Instance - //Add an after block here to delete that Instance + describe(`${igType} Groups: User access Tab`, () => { + let user: AwxUser; + let instanceGroup: InstanceGroup; - it.skip('can visit the instances tab of an instance group and associate an instance to that instance group, then disable the instance', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances tab - //Assert the association of the Instance to the IG - //Assert the Instance being disabled + before(() => { + cy.awxLogin(); }); - it.skip('can visit the instances tab of an instance group and disassociate an instance from that instance group', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances tab - //Assert the disassociation of the instance from the IG + beforeEach(function () { + cy.createAwxInstanceGroup( + igType === 'Container' + ? { + name: 'E2E Container Group ' + randomString(4), + is_container_group: true, + max_concurrent_jobs: 0, + max_forks: 0, + pod_spec_override: '', + } + : { + name: 'E2E Instance Group ' + randomString(4), + percent_capacity_remaining: 100, + policy_instance_minimum: 0, + } + ).then((ig: InstanceGroup) => { + instanceGroup = ig; + }); + cy.createAwxUser(this.globalOrganization as Organization).then((u) => { + user = u; + }); + cy.navigateTo('awx', 'instance-groups'); + cy.verifyPageTitle('Instance Groups'); }); - it.skip('can visit the instances tab of an instance group and bulk disassociate multiple instances from that instance group', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances tab - //Create one additional instance in this test to allow for bulk deletion - //Assert the deletion + afterEach(() => { + cy.deleteAwxInstanceGroup(instanceGroup, { failOnStatusCode: false }); + cy.deleteAwxUser(user, { failOnStatusCode: false }); }); - it.skip('can visit the instances tab of an instance group and run a health check against an instance', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances tab - //Assert the presence of the health check button - //After running the health check, assert the expected UI behavior/results + it(`can visit the ${igType} group -> user access tab, add a user, view the user on the user list and then delete user`, () => { + cy.filterTableBySingleSelect('name', instanceGroup.name); + cy.get('[data-cy="name-column-cell"]').within(() => { + cy.get('a').click(); + }); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes(`infrastructure/instance_groups/${igType.toLowerCase()}-group`)) + .to.be.true; + }); + cy.clickTab(/^User access$/, true); + cy.get('.pf-v5-c-empty-state__title-text').contains( + /^There are currently no users assigned to this instance group./ + ); + cy.get('.pf-v5-c-empty-state__body').contains(/^Add a role by clicking the button below./); + cy.getByDataCy('add-roles').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('infrastructure/instance_groups/')).to.be.true; + expect(currentUrl.includes('instance-groups/users/add-users')).to.be.true; + }); + cy.get('[data-cy="wizard-nav"] li').eq(0).should('contain.text', 'Select user(s)'); + cy.get('[data-cy="wizard-nav"] li').eq(1).should('contain.text', 'Select roles to apply'); + cy.get('[data-cy="wizard-nav"] li').eq(2).should('contain.text', 'Review'); + cy.get('.pf-v5-c-page__main-body > .pf-v5-c-title').should('have.text', 'Select user(s)'); + cy.selectTableRowByCheckbox('username', user.username, { disableFilter: false }); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); + }); + cy.getByDataCy('Submit').click(); + cy.get('.pf-v5-c-page__main-body > .pf-v5-c-title').should( + 'have.text', + 'Select roles to apply' + ); + cy.searchAndDisplayResource('Admin'); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); + cy.get('[data-cy="checkbox-column-cell"] input').click(); + }); + cy.getByDataCy('Submit').click(); + cy.get('.pf-v5-c-page__main-body > .pf-v5-c-title').should('have.text', 'Review'); + cy.get('[data-cy="expandable-section-users"]') + .should('be.visible') + .within(() => { + cy.get('tbody tr').should('have.length', 1); + cy.get('[data-cy="username-column-cell"]').should('have.text', user.username); + }); + cy.get('[data-cy="expandable-section-awxRoles"]').should('be.visible'); + cy.intercept('POST', awxAPI`/role_user_assignments/`).as('userAdded'); + cy.getByDataCy('Submit').click(); + cy.wait('@userAdded') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(201); + }); + cy.visit( + `/infrastructure/instance_groups/${igType.toLowerCase()}-groups/${instanceGroup.id}/user-access` + ); + cy.verifyPageTitle(instanceGroup.name); + cy.get('[data-cy="text-input"]').find('input').type(user.username); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); + cy.get('[data-cy="remove-role"]').click(); + }); + cy.intercept('DELETE', awxAPI`/role_user_assignments/*/`).as('userRemoved'); + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains('Remove role'); + cy.get('button').contains('Remove role').should('have.attr', 'aria-disabled', 'true'); + cy.getByDataCy('username-column-cell').should('have.text', user.username); + cy.get('input[id="confirm"]').click(); + cy.get('button').contains('Remove role').click(); + }); + cy.wait('@userRemoved') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); + }); }); }); +}); - describe('Instance Groups: Instances Tab -> Instances Details Page', () => { - //Add a before block here to create an Instance - //Add an after block here to delete that Instance +describe('Instance Groups: Jobs Tab', () => { + let inventory: Inventory; + let job_template: JobTemplate; + let instanceGroupDefault: InstanceGroup; - it.skip('can visit the details page of an instance nested inside an instance group and run health check on it', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances -> details tab - //Assert the presence of the health check button - //After running the health check, assert the expected UI behavior/results + before(() => { + cy.awxLogin(); + }); + + beforeEach(function () { + cy.getAwxInstanceGroupByName('default') + .its('results[0]') + .then((ig: InstanceGroup) => { + instanceGroupDefault = ig; + cy.createAwxInventory().then((inv) => { + inventory = inv; + cy.createAwxJobTemplate( + { + organization: (this.globalOrganization as Organization).id, + project: (this.globalProject as Project).id, + inventory: inventory.id, + }, + ig + ).then((result) => { + job_template = result; + }); + }); + }); + }); + + afterEach(() => { + cy.deleteAwxInventory(inventory, { failOnStatusCode: false }); + cy.deleteAwxJobTemplate(job_template, { failOnStatusCode: false }); + }); + + it('can visit the instance group -> jobs tab, trigger a job, let the job finish, then view the job on the jobs list tab of the IG and delete job', () => { + cy.navigateTo('awx', 'templates'); + cy.verifyPageTitle('Templates'); + + cy.filterTableBySingleSelect('name', job_template.name); + cy.clickTableRowPinnedAction(job_template.name, 'launch-template', false); + cy.verifyPageTitle(job_template.name); + + cy.navigateTo('awx', 'instance-groups'); + cy.verifyPageTitle('Instance Groups'); + + cy.filterTableBySingleSelect('name', instanceGroupDefault.name); + cy.get('[data-cy="name-column-cell"]').within(() => { + cy.get('a').click(); + }); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes('infrastructure/instance_groups/container-group')).to.be.true; }); + cy.clickTab(/^Jobs$/, true); + cy.filterTableBySingleSelect('name', job_template.name); + cy.getBy('tbody').within(() => { + cy.get('[data-cy="cancel-job"]', { timeout: 60000 }).should('not.exist'); + cy.clickKebabAction('actions-dropdown', 'delete-job'); + }); + + cy.intercept('DELETE', awxAPI`/jobs/*/`).as('deleted'); - it.skip('can visit the details page of an instance nested inside an instance group and disassociate the instance from the IG', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances -> details tab - //Assert the disassociation of the instance from the IG + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains('Permanently delete job'); + cy.get('button').contains('Delete job').should('have.attr', 'aria-disabled', 'true'); + cy.getByDataCy('name-column-cell').should('have.text', job_template.name); + cy.get('input[id="confirm"]').click(); + cy.get('button').contains('Delete job').click(); }); + cy.wait('@deleted') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); + }); + cy.clickModalButton('Close'); + cy.get('.pf-v5-c-empty-state__title-text').contains(/^No results found/); + cy.get('.pf-v5-c-empty-state__body').contains( + /^No results match this filter criteria. Clear all filters and try again./ + ); + }); +}); - it.skip('can visit the details page of an instance nested inside an instance group and change the capacity adjustment and disable the instance', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances -> details tab - //Assert presence of capacity adjustment bar and its original setting - //Assert edited setting of capacity adjustment bar - //Disable the instance and assert the disablement +describe('Instance Groups: Instances Tab', () => { + let instance: Instance; + let instanceGroup: InstanceGroup; + + before(() => { + cy.awxLogin(); + }); + + beforeEach(() => { + cy.createAwxInstance('E2EInstanceIGTest' + randomString(5), 9999).then((ins: Instance) => { + instance = ins; + cy.createAwxInstanceGroup({ + name: 'E2E Instance Group Instance tab test' + randomString(4), + percent_capacity_remaining: 100, + policy_instance_minimum: 0, + policy_instance_list: !Cypress.currentTest.title.includes('associate an instance') + ? [instance.hostname] + : [], + }).then((ig: InstanceGroup) => { + instanceGroup = ig; + cy.navigateTo('awx', 'instance-groups'); + cy.verifyPageTitle('Instance Groups'); + }); + }); + }); + + afterEach(() => { + cy.removeAwxInstance(instance?.id.toString()); + cy.deleteAwxInstanceGroup(instanceGroup, { failOnStatusCode: false }); + }); + + it('can visit the instances tab of an instance group and associate an instance to that instance group, then disable the instance', () => { + cy.filterTableBySingleSelect('name', instanceGroup.name); + cy.get('[data-cy="name-column-cell"]').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes('infrastructure/instance_groups')).to.be.true; + }); + + cy.clickTab(/^Instances$/, true); + cy.getByDataCy('empty-state-title').contains('There are currently no instances added'); + cy.get('[data-cy="Please associate an instance by using the button below."]').should( + 'be.visible' + ); + cy.getByDataCy('associate-instance').click(); + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains('Select instances'); + cy.get('button').contains('Confirm').should('have.attr', 'aria-disabled', 'true'); + cy.filterTableBySingleSelect('hostname', instance.hostname); + cy.intercept('POST', awxAPI`/instance_groups/${instanceGroup.id.toString()}/instances/`).as( + 'associateInstance' + ); + cy.getByDataCy('checkbox-column-cell').find('input').click(); + cy.get('button').contains('Confirm').click(); }); + cy.assertModalSuccess(); + cy.wait('@associateInstance') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); + }); + cy.clickModalButton('Close'); + cy.intercept('PATCH', awxAPI`/instances/*/`).as('disableInstance'); + cy.getByDataCy('toggle-switch').should('be.visible').click(); + cy.wait('@disableInstance') + .then((response) => { + expect(response?.response?.statusCode).to.eql(200); + }) + .its('response.body.enabled') + .then((enabled: string) => { + expect(enabled).to.be.false; + }); }); - describe('Instance Groups: Jobs Tab', () => { - //Add a beforeEach block and use it to create a job template utilizing the IG created in the original beforeEach block - //Add a command to trigger the launch of the job template - //Add an afterEach block to delete the job template + it('can visit the instances tab of an instance group and bulk disassociate instances from that instance group', () => { + let instanceGroupDisassociate: InstanceGroup; + let instanceToAssociate: Instance; + const arrayOfElementText = ['']; + const arrayOfInstance = []; + for (let i = 0; i < 5; i++) { + cy.createAwxInstance('E2EInstanceToDisassociateFromIG' + randomString(5), 9999).then( + (ins: Instance) => { + instanceToAssociate = ins; + arrayOfElementText.push(instanceToAssociate.hostname); + arrayOfInstance.push(instanceToAssociate); + } + ); + } + + cy.createAwxInstanceGroup({ + name: 'E2E Instance Group Disassociate' + randomString(4), + percent_capacity_remaining: 100, + policy_instance_minimum: 0, + policy_instance_list: arrayOfElementText, + }).then((ig: InstanceGroup) => { + instanceGroupDisassociate = ig; + cy.filterTableBySingleSelect('name', instanceGroupDisassociate?.name); + cy.get('[data-cy="name-column-cell"]').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes('infrastructure/instance_groups')).to.be.true; + }); + cy.clickTab(/^Instances$/, true); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 5); + }); + cy.get('button').contains('Disassociate').should('have.attr', 'aria-disabled', 'true'); + cy.getByDataCy('select-all').click(); + cy.get('button') + .contains('Disassociate') + .should('have.attr', 'aria-disabled', 'false') + .click(); + cy.intercept( + 'POST', + awxAPI`/instance_groups/${instanceGroupDisassociate.id.toString()}/instances/` + ).as('disassociateInstance'); + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains('Disassociate instance from instance group'); + cy.get('button') + .contains('Disassociate instances') + .should('have.attr', 'aria-disabled', 'true'); + cy.get('input[id="confirm"]').click(); + cy.get('button') + .contains('Disassociate instances') + .should('have.attr', 'aria-disabled', 'false') + .click(); + }); + cy.assertModalSuccess(); + cy.wait('@disassociateInstance') + .its('response') + .then((response) => { + expect(response?.statusCode).to.eql(204); + }); + cy.clickModalButton('Close'); + cy.getByDataCy('empty-state-title').contains('There are currently no instances added'); + cy.get('[data-cy="Please associate an instance by using the button below."]').should( + 'be.visible' + ); + cy.deleteAwxInstanceGroup(instanceGroupDisassociate, { failOnStatusCode: false }); + arrayOfInstance.map(({ id }) => cy.removeAwxInstance(id?.toString())); + }); + }); - it.skip('can visit the instance group -> jobs tab, trigger a job, let the job finish, then view the job on the jobs list tab of the IG', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances -> jobs tab - //Assert the expected job in the list + it('can visit the instances tab of an instance group and run a health check from toolbar against an instance', () => { + cy.filterTableBySingleSelect('name', instanceGroup.name); + cy.get('[data-cy="name-column-cell"]').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes('infrastructure/instance_groups')).to.be.true; + }); + cy.clickTab(/^Instances$/, true); + cy.get('button').contains('Run health check').should('have.attr', 'aria-disabled', 'true'); + cy.filterTableBySingleSelect('hostname', instance.hostname); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); + cy.get('[data-cy="checkbox-column-cell"] input').click(); + }); + cy.getBy('[data-ouia-component-id="page-toolbar"]').within(() => { + cy.getByDataCy('run-health-check').click(); }); + cy.intercept('POST', awxAPI`/instances/${instance.id.toLocaleString()}/health_check/`).as( + 'runHealthCheck' + ); + cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { + cy.get('header').contains('Run health checks on these instances'); + cy.get('button').contains('Run health check').should('have.attr', 'aria-disabled', 'true'); + cy.getByDataCy('name-column-cell').should('have.text', instance.hostname); + cy.get('input[id="confirm"]').click(); + cy.get('button').contains('Run health check').click(); + }); + cy.assertModalSuccess(); + cy.clickModalButton('Close'); + cy.wait('@runHealthCheck') + .then((response) => { + expect(response.response?.statusCode).to.eql(200); + }) + .its('response.body.msg') + .then((response) => { + expect(response).contains(`Health check is running for ${instance.hostname}.`); + }); + }); - it.skip('can visit the instance group -> jobs tab and relaunch a job and then immediately cancel that job associated with that instance group', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances -> jobs tab - //Assert the presence of the relaunch button - //Assert the relaunch trigger - //Assert the cancellation of the job launch + it('can visit the instances tab of an instance group and run a health check from row against an instance', () => { + cy.filterTableBySingleSelect('name', instanceGroup.name); + cy.get('[data-cy="name-column-cell"]').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes('infrastructure/instance_groups')).to.be.true; }); + cy.clickTab(/^Instances$/, true); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); + }); + cy.filterTableBySingleSelect('hostname', instance.hostname); + cy.intercept('POST', awxAPI`/instances/${instance.id.toLocaleString()}/health_check/`).as( + 'runHealthCheck' + ); + cy.clickTableRowPinnedAction(instance.hostname, 'run-health-check', false); + cy.wait('@runHealthCheck') + .then((response) => { + expect(response.response?.statusCode).to.eql(200); + }) + .its('response.body.msg') + .then((response) => { + expect(response).contains(`Health check is running for ${instance.hostname}.`); + }); + }); - it.skip('can visit the instance group -> jobs tab and delete a job associated with that instance group', () => { - //Utilize the IG created in the beforeEach block - //Assert the navigation to the instances -> jobs tab - //Assert the presence of the delete button - //Assert the deletion of the job + it('can visit the details page of an instance nested inside an instance group and run health check on it', () => { + cy.filterTableBySingleSelect('name', instanceGroup.name); + cy.get('[data-cy="name-column-cell"]').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes('infrastructure/instance_groups')).to.be.true; + }); + cy.clickTab(/^Instances$/, true); + cy.get('[data-ouia-component-id="simple-table"]').within(() => { + cy.get('tbody tr').should('have.length', 1); }); + cy.filterTableBySingleSelect('hostname', instance.hostname); + cy.get('[data-cy="name-column-cell"]').click(); + cy.url().then((currentUrl) => { + expect(currentUrl.includes('details')).to.be.true; + expect(currentUrl.includes('infrastructure/instance_groups')).to.be.true; + }); + cy.verifyPageTitle(instance.hostname); + cy.contains('nav[aria-label="Breadcrumb"]', 'Instance groups').should('exist'); + cy.contains('nav[aria-label="Breadcrumb"]', instanceGroup.name).should('exist'); + cy.contains('nav[aria-label="Breadcrumb"]', 'Instances').should('exist'); + cy.contains('nav[aria-label="Breadcrumb"]', instance.hostname).should('exist'); + cy.contains('nav[aria-label="Breadcrumb"]', 'Details').should('exist'); + + cy.intercept('POST', awxAPI`/instances/${instance.id.toString()}/health_check/`).as( + 'runHealthCheck' + ); + cy.getByDataCy('run-health-check').click(); + cy.wait('@runHealthCheck') + .then((response) => { + expect(response.response?.statusCode).to.eql(200); + }) + .its('response.body.msg') + .then((response) => { + expect(response).contains(`Health check is running for ${instance.hostname}.`); + }); + cy.get('button').contains('Run health check').should('have.attr', 'aria-disabled', 'true'); }); }); diff --git a/cypress/support/awx-commands.ts b/cypress/support/awx-commands.ts index 2b4e14057a..3b6d576a29 100644 --- a/cypress/support/awx-commands.ts +++ b/cypress/support/awx-commands.ts @@ -1014,7 +1014,8 @@ Cypress.Commands.add( jobTemplate: SetRequired< Partial>, 'organization' | 'project' | 'inventory' - > + >, + instanceGroup?: InstanceGroup ) => { cy.requestPost< SetRequired>, 'organization' | 'project' | 'inventory'>, @@ -1023,6 +1024,15 @@ Cypress.Commands.add( name: 'E2E Job Template ' + randomString(4), playbook: 'playbooks/hello_world.yml', ...jobTemplate, + }).then((jt: Partial) => { + if (instanceGroup) { + if (jt.id) { + cy.awxRequestPost(awxAPI`/job_templates/${jt.id.toString()}/instance_groups/`, { + id: instanceGroup.id, + }); + } + } + return new Promise((resolve, _reject) => resolve(jt)); }); } ); @@ -1260,20 +1270,22 @@ Cypress.Commands.add( } ); -Cypress.Commands.add( - 'createAwxInstanceGroup', - (instanceGroup?: Partial>) => { - cy.awxRequestPost>, InstanceGroup>( - awxAPI`/instance_groups/`, - { - name: 'E2E Instance Group ' + randomString(4), - percent_capacity_remaining: 100, - policy_instance_minimum: 100, - ...instanceGroup, - } - ); - } -); +Cypress.Commands.add('createAwxInstanceGroup', (instanceGroup?: Partial) => { + cy.requestPost( + awxAPI`/instance_groups/`, + instanceGroup ?? { + name: 'E2E Instance Group ' + randomString(4), + percent_capacity_remaining: 100, + policy_instance_minimum: 0, + } + ).then((instanceGroup) => instanceGroup); +}); + +Cypress.Commands.add('getAwxInstanceGroupByName', (instanceGroupName: string) => { + cy.awxRequestGet>( + awxAPI`/instance_groups/?name=${instanceGroupName}` + ); +}); Cypress.Commands.add( 'deleteAwxInstanceGroup', diff --git a/cypress/support/commands.d.ts b/cypress/support/commands.d.ts index 507c784e7b..eab1ac0436 100644 --- a/cypress/support/commands.d.ts +++ b/cypress/support/commands.d.ts @@ -742,7 +742,8 @@ declare global { jobTemplate: SetRequired< Partial>, 'organization' | 'project' | 'inventory' - > + >, + instanceGroup?: InstanceGroup ): Chainable; createTemplateSurvey( @@ -780,6 +781,8 @@ declare global { awxWorkflowJobTemplateName: string ): Chainable; + getAwxInstanceGroupByName(instanceGroupName: string): Chainable; + renderWorkflowVisualizerNodesFromFixtureFile( workflowJobTemplateName: string, fixtureFile: string @@ -868,9 +871,7 @@ declare global { getAwxJobTemplateByName(awxJobTemplateName: string): Chainable; createAwxTeam(organization: Organization): Chainable; createAwxUser(organization: Organization): Chainable; - createAwxInstanceGroup( - instanceGroup?: Partial> - ): Chainable; + createAwxInstanceGroup(instanceGroup?: Partial): Chainable; createAwxInstance(hostname: string, listener_port?: number): Chainable; createAwxLabel(label: Partial>): Chainable