diff --git a/frontend/.github/workflows/e2e.groups.yml b/frontend/.github/workflows/e2e.groups.yml new file mode 100644 index 0000000000..7eb5e056e6 --- /dev/null +++ b/frontend/.github/workflows/e2e.groups.yml @@ -0,0 +1,25 @@ +name: e2e:groups +# https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: [deployment_status] +jobs: + e2e: + # only runs this job on successful deploy + if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: | + echo "$GITHUB_CONTEXT" + - name: Checkout + uses: actions/checkout@v3 + - name: Run Cypress + uses: cypress-io/github-action@v2 + with: + env: AUTH_USER=admin,AUTH_PASSWORD=unleash4all + config: baseUrl=${{ github.event.deployment_status.target_url }} + record: true + spec: cypress/integration/groups/groups.spec.ts + env: + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} diff --git a/frontend/cypress/integration/groups/groups.spec.ts b/frontend/cypress/integration/groups/groups.spec.ts new file mode 100644 index 0000000000..6977641946 --- /dev/null +++ b/frontend/cypress/integration/groups/groups.spec.ts @@ -0,0 +1,160 @@ +/// + +export {}; +const baseUrl = Cypress.config().baseUrl; +const randomId = String(Math.random()).split('.')[1]; +const groupName = `unleash-e2e-${randomId}`; +const userIds: any[] = []; + +// Disable all active splash pages by visiting them. +const disableActiveSplashScreens = () => { + cy.visit(`/splash/operators`); +}; + +describe('groups', () => { + before(() => { + disableActiveSplashScreens(); + cy.login(); + for (let i = 1; i <= 2; i++) { + cy.request('POST', `${baseUrl}/api/admin/user-admin`, { + name: `unleash-e2e-user${i}-${randomId}`, + email: `unleash-e2e-user${i}-${randomId}@test.com`, + sendEmail: false, + rootRole: 3, + }).then(response => userIds.push(response.body.id)); + } + }); + + after(() => { + userIds.forEach(id => + cy.request('DELETE', `${baseUrl}/api/admin/user-admin/${id}`) + ); + }); + + beforeEach(() => { + cy.login(); + cy.visit('/admin/groups'); + if (document.querySelector("[data-testid='CLOSE_SPLASH']")) { + cy.get("[data-testid='CLOSE_SPLASH']").click(); + } + }); + + it('gives an error if a group does not have an owner', () => { + cy.get("[data-testid='NAVIGATE_TO_CREATE_GROUP']").click(); + + cy.intercept('POST', '/api/admin/groups').as('createGroup'); + + cy.get("[data-testid='UG_NAME_ID']").type(groupName); + cy.get("[data-testid='UG_DESC_ID']").type('hello-world'); + cy.get("[data-testid='UG_USERS_ID']").click(); + cy.contains(`unleash-e2e-user1-${randomId}`).click(); + cy.get("[data-testid='UG_USERS_ADD_ID']").click(); + + cy.get("[data-testid='UG_CREATE_BTN_ID']").click(); + cy.get("[data-testid='TOAST_TEXT']").contains( + 'Group needs to have at least one Owner' + ); + }); + + it('can create a group', () => { + cy.get("[data-testid='NAVIGATE_TO_CREATE_GROUP']").click(); + + cy.intercept('POST', '/api/admin/groups').as('createGroup'); + + cy.get("[data-testid='UG_NAME_ID']").type(groupName); + cy.get("[data-testid='UG_DESC_ID']").type('hello-world'); + cy.get("[data-testid='UG_USERS_ID']").click(); + cy.contains(`unleash-e2e-user1-${randomId}`).click(); + cy.get("[data-testid='UG_USERS_ADD_ID']").click(); + cy.get("[data-testid='UG_USERS_TABLE_ROLE_ID']").click(); + cy.contains('Owner').click(); + + cy.get("[data-testid='UG_CREATE_BTN_ID']").click(); + cy.wait('@createGroup'); + cy.contains(groupName); + }); + + it('gives an error if a group exists with the same name', () => { + cy.get("[data-testid='NAVIGATE_TO_CREATE_GROUP']").click(); + + cy.intercept('POST', '/api/admin/groups').as('createGroup'); + + cy.get("[data-testid='UG_NAME_ID']").type(groupName); + cy.get("[data-testid='UG_DESC_ID']").type('hello-world'); + cy.get("[data-testid='UG_USERS_ID']").click(); + cy.contains(`unleash-e2e-user1-${randomId}`).click(); + cy.get("[data-testid='UG_USERS_ADD_ID']").click(); + cy.get("[data-testid='UG_USERS_TABLE_ROLE_ID']").click(); + cy.contains('Owner').click(); + + cy.get("[data-testid='UG_CREATE_BTN_ID']").click(); + cy.get("[data-testid='TOAST_TEXT']").contains( + 'Group name already exists' + ); + }); + + it('can edit a group', () => { + cy.contains(groupName).click(); + + cy.get("[data-testid='UG_EDIT_BTN_ID']").click(); + + cy.get("[data-testid='UG_DESC_ID']").type('-my edited description'); + + cy.get("[data-testid='UG_SAVE_BTN_ID']").click(); + + cy.contains('hello-world-my edited description'); + }); + + it('can add user to a group', () => { + cy.contains(groupName).click(); + + cy.get("[data-testid='UG_ADD_USER_BTN_ID']").click(); + + cy.get("[data-testid='UG_USERS_ID']").click(); + cy.contains(`unleash-e2e-user2-${randomId}`).click(); + cy.get("[data-testid='UG_USERS_ADD_ID']").click(); + + cy.get("[data-testid='UG_SAVE_BTN_ID']").click(); + + cy.contains(`unleash-e2e-user1-${randomId}`); + cy.contains(`unleash-e2e-user2-${randomId}`); + cy.get("td span:contains('Owner')").should('have.length', 1); + cy.get("td span:contains('Member')").should('have.length', 1); + }); + + it('can edit user role in a group', () => { + cy.contains(groupName).click(); + + cy.get(`[data-testid='UG_EDIT_USER_BTN_ID-${userIds[1]}']`).click(); + + cy.get("[data-testid='UG_USERS_ROLE_ID']").click(); + cy.get("li[data-value='Owner']").click(); + + cy.get("[data-testid='UG_SAVE_BTN_ID']").click(); + + cy.contains(`unleash-e2e-user1-${randomId}`); + cy.contains(`unleash-e2e-user2-${randomId}`); + cy.get("td span:contains('Owner')").should('have.length', 2); + cy.contains('Member').should('not.exist'); + }); + + it('can remove user from a group', () => { + cy.contains(groupName).click(); + + cy.get(`[data-testid='UG_REMOVE_USER_BTN_ID-${userIds[1]}']`).click(); + + cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click(); + + cy.contains(`unleash-e2e-user1-${randomId}`); + cy.contains(`unleash-e2e-user2-${randomId}`).should('not.exist'); + }); + + it('can delete a group', () => { + cy.contains(groupName).click(); + + cy.get("[data-testid='UG_DELETE_BTN_ID']").click(); + cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click(); + + cy.contains(groupName).should('not.exist'); + }); +}); diff --git a/frontend/src/component/addons/AddonForm/AddonForm.tsx b/frontend/src/component/addons/AddonForm/AddonForm.tsx index f7bf2a49e2..3a19477149 100644 --- a/frontend/src/component/addons/AddonForm/AddonForm.tsx +++ b/frontend/src/component/addons/AddonForm/AddonForm.tsx @@ -33,6 +33,7 @@ import { StyledButtonSection, } from './AddonForm.styles'; import { useTheme } from '@mui/system'; +import { GO_BACK } from 'constants/navigate'; interface IAddonFormProps { provider?: IAddonProvider; @@ -168,7 +169,7 @@ export const AddonForm: VFC = ({ }; const onCancel = () => { - navigate(-1); + navigate(GO_BACK); }; const onSubmit: FormEventHandler = async event => { diff --git a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx index 07b9c0eb09..203f1ac1fb 100644 --- a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx +++ b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx @@ -12,6 +12,7 @@ import { useState } from 'react'; import { scrollToTop } from 'component/common/util'; import { formatUnknownError } from 'utils/formatUnknownError'; import { usePageTitle } from 'hooks/usePageTitle'; +import { GO_BACK } from 'constants/navigate'; const pageTitle = 'Create API token'; @@ -75,7 +76,7 @@ export const CreateApiToken = () => { }; const handleCancel = () => { - navigate(-1); + navigate(GO_BACK); }; return ( diff --git a/frontend/src/component/admin/groups/CreateGroup/CreateGroup.tsx b/frontend/src/component/admin/groups/CreateGroup/CreateGroup.tsx index 8b6bcec4d7..8cb9dec9cb 100644 --- a/frontend/src/component/admin/groups/CreateGroup/CreateGroup.tsx +++ b/frontend/src/component/admin/groups/CreateGroup/CreateGroup.tsx @@ -9,6 +9,7 @@ import { formatUnknownError } from 'utils/formatUnknownError'; import { UG_CREATE_BTN_ID } from 'utils/testIds'; import { Button } from '@mui/material'; import { CREATE } from 'constants/misc'; +import { GO_BACK } from 'constants/navigate'; export const CreateGroup = () => { const { setToastData, setToastApiError } = useToast(); @@ -58,7 +59,7 @@ export const CreateGroup = () => { }; const handleCancel = () => { - navigate(-1); + navigate(GO_BACK); }; return ( diff --git a/frontend/src/component/admin/groups/EditGroup/EditGroup.tsx b/frontend/src/component/admin/groups/EditGroup/EditGroup.tsx index ab0f3a1972..2efac8018e 100644 --- a/frontend/src/component/admin/groups/EditGroup/EditGroup.tsx +++ b/frontend/src/component/admin/groups/EditGroup/EditGroup.tsx @@ -10,6 +10,8 @@ import { Button } from '@mui/material'; import { EDIT } from 'constants/misc'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useGroup } from 'hooks/api/getters/useGroup/useGroup'; +import { UG_SAVE_BTN_ID } from 'utils/testIds'; +import { GO_BACK } from 'constants/navigate'; export const EditGroup = () => { const groupId = Number(useRequiredPathParam('groupId')); @@ -40,7 +42,7 @@ export const EditGroup = () => { try { await updateGroup(groupId, payload); refetchGroup(); - navigate(-1); + navigate(GO_BACK); setToastData({ title: 'Group updated successfully', type: 'success', @@ -60,7 +62,7 @@ export const EditGroup = () => { }; const handleCancel = () => { - navigate(-1); + navigate(GO_BACK); }; return ( @@ -85,7 +87,12 @@ export const EditGroup = () => { mode={EDIT} clearErrors={clearErrors} > - diff --git a/frontend/src/component/admin/groups/Group/AddGroupUser/AddGroupUser.tsx b/frontend/src/component/admin/groups/Group/AddGroupUser/AddGroupUser.tsx index 34e2729efc..a3b1577dcb 100644 --- a/frontend/src/component/admin/groups/Group/AddGroupUser/AddGroupUser.tsx +++ b/frontend/src/component/admin/groups/Group/AddGroupUser/AddGroupUser.tsx @@ -10,6 +10,7 @@ import { FC, FormEvent, useEffect, useMemo, useState } from 'react'; import { formatUnknownError } from 'utils/formatUnknownError'; import { GroupFormUsersSelect } from 'component/admin/groups/GroupForm/GroupFormUsersSelect/GroupFormUsersSelect'; import { GroupFormUsersTable } from 'component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable'; +import { UG_SAVE_BTN_ID } from 'utils/testIds'; const StyledForm = styled('form')(() => ({ display: 'flex', @@ -142,6 +143,7 @@ export const AddGroupUser: FC = ({ type="submit" variant="contained" color="primary" + data-testid={UG_SAVE_BTN_ID} > Save diff --git a/frontend/src/component/admin/groups/Group/EditGroupUser/EditGroupUser.tsx b/frontend/src/component/admin/groups/Group/EditGroupUser/EditGroupUser.tsx index bab86186d3..cdf1214340 100644 --- a/frontend/src/component/admin/groups/Group/EditGroupUser/EditGroupUser.tsx +++ b/frontend/src/component/admin/groups/Group/EditGroupUser/EditGroupUser.tsx @@ -8,6 +8,7 @@ import useToast from 'hooks/useToast'; import { IGroup, IGroupUser, Role } from 'interfaces/group'; import { FC, FormEvent, useEffect, useMemo, useState } from 'react'; import { formatUnknownError } from 'utils/formatUnknownError'; +import { UG_SAVE_BTN_ID, UG_USERS_ROLE_ID } from 'utils/testIds'; const StyledForm = styled('form')(() => ({ display: 'flex', @@ -143,6 +144,7 @@ export const EditGroupUser: FC = ({ Assign the role the user should have in this group @@ -159,6 +161,7 @@ export const EditGroupUser: FC = ({ diff --git a/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx b/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx index 6382e56dc7..64186f2cbe 100644 --- a/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx +++ b/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx @@ -4,6 +4,7 @@ import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { Role } from 'interfaces/group'; import { Badge } from 'component/common/Badge/Badge'; import { StarRounded } from '@mui/icons-material'; +import { UG_USERS_TABLE_ROLE_ID } from 'utils/testIds'; const StyledPopupStar = styled(StarRounded)(({ theme }) => ({ color: theme.palette.warning.main, @@ -36,6 +37,7 @@ export const GroupUserRoleCell = ({ condition={Boolean(onChange)} show={