From c3eb411b36d6c15a11c04ce9dc58fae2a84c3f07 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Tue, 10 Dec 2024 15:42:06 +0200 Subject: [PATCH] [GEN-1972]: re-usability for Cypress tests (constants & functions) (#1962) This pull request includes significant changes to the Cypress tests in the `frontend/webapp` directory. The primary focus is on refactoring the test files to use constants and utility functions for better readability and maintainability. Refactoring and Constants: * [`frontend/webapp/cypress/constants/index.ts`](diffhunk://#diff-ffe3e5bc9ff7bba75f2007551dd3e69fa93e9638a7fb62ceec23414f8333f491R1-R98): Added various constants such as `ROUTES`, `CRD_NAMES`, `CRD_IDS`, `NAMESPACES`, `SELECTED_ENTITIES`, `DATA_IDS`, `BUTTONS`, `INPUTS`, and `TEXTS` for use across Cypress tests. Test File Updates: * [`frontend/webapp/cypress/e2e/01-connection.cy.ts`](diffhunk://#diff-3b15989bf88d11caa43bab5ca47381e3dd6da8fd0151a06c6fe677c05129f662L1-R7): Updated import paths to use the new constants file and refactored the test to use `ROUTES.ROOT` for the root path. * [`frontend/webapp/cypress/e2e/02-onboarding.cy.ts`](diffhunk://#diff-d5819393774dc93d8cee9a6f11c4f6c459bdfa2b031b263bbba79dad3a342dcdL1-R26): Updated import paths and refactored tests to use constants like `BUTTONS`, `DATA_IDS`, `ROUTES`, and `SELECTED_ENTITIES`. * [`frontend/webapp/cypress/e2e/03-sources.cy.ts`](diffhunk://#diff-981488421b684f7ca98e7e8b147b14c23fe2c57e876d88ca981c563c8ba003cdL1-R28): Refactored the tests to use utility functions (`getCrdById`, `getCrdIds`, `updateEntity`) and constants for better readability and maintainability. [[1]](diffhunk://#diff-981488421b684f7ca98e7e8b147b14c23fe2c57e876d88ca981c563c8ba003cdL1-R28) [[2]](diffhunk://#diff-981488421b684f7ca98e7e8b147b14c23fe2c57e876d88ca981c563c8ba003cdL51-R66) * [`frontend/webapp/cypress/e2e/04-destinations.cy.ts`](diffhunk://#diff-6f34bd7eb1cce2b4683938973757c4b0e80921234633a259cd17674c0805ca1bL1-R67): Similar refactoring as in `03-sources.cy.ts`, using utility functions and constants. * [`frontend/webapp/cypress/e2e/05-actions.cy.ts`](diffhunk://#diff-adc830d3310d3ba00c598b867a2b4f782e4fc6d3147fdb7af3f6346eb6d2f79fL1-R66): Refactored to use utility functions and constants, ensuring consistency across test files. --- frontend/webapp/cypress/constants/index.ts | 98 ++++++++++++++ .../webapp/cypress/e2e/01-connection.cy.ts | 8 +- .../webapp/cypress/e2e/02-onboarding.cy.ts | 32 ++--- frontend/webapp/cypress/e2e/03-sources.cy.ts | 106 +++++---------- .../webapp/cypress/e2e/04-destinations.cy.ts | 122 ++++++------------ frontend/webapp/cypress/e2e/05-actions.cy.ts | 117 ++++++----------- frontend/webapp/cypress/e2e/06-rules.cy.ts | 113 ++++++---------- frontend/webapp/cypress/functions/index.ts | 93 +++++++++++++ frontend/webapp/utils/constants/routes.tsx | 1 + 9 files changed, 364 insertions(+), 326 deletions(-) create mode 100644 frontend/webapp/cypress/constants/index.ts create mode 100644 frontend/webapp/cypress/functions/index.ts diff --git a/frontend/webapp/cypress/constants/index.ts b/frontend/webapp/cypress/constants/index.ts new file mode 100644 index 000000000..3ee26dbb6 --- /dev/null +++ b/frontend/webapp/cypress/constants/index.ts @@ -0,0 +1,98 @@ +export const ROUTES = { + ROOT: '/', + CHOOSE_SOURCES: '/choose-sources', + CHOOSE_DESTINATION: '/choose-destination', + OVERVIEW: '/overview', +}; + +export const CRD_NAMES = { + SOURCE: 'instrumentationconfigs.odigos.io', + DESTINATION: 'destinations.odigos.io', + ACTION: 'piimaskings.actions.odigos.io', + INSTRUMENTATION_RULE: 'instrumentationrules.odigos.io', +}; + +export const CRD_IDS = { + SOURCE: 'deployment-frontend', + DESTINATION: '', + ACTION: '', + INSTRUMENTATION_RULE: '', +}; + +export const NAMESPACES = { + DEFAULT: 'default', + ODIGOS_SYSTEM: 'odigos-system', +}; + +export const SELECTED_ENTITIES = { + NAMESPACE: NAMESPACES.DEFAULT, + SOURCE: 'frontend', + DESTINATION: 'Jaeger', + ACTION: 'PiiMasking', + INSTRUMENTATION_RULE: 'PayloadCollection', +}; + +export const DATA_IDS = { + SELECT_NAMESPACE: '[data-id=namespace-default]', + SELECT_DESTINATION: '[data-id=destination-jaeger]', + SELECT_DESTINATION_AUTOFILL_FIELD: '[data-id=JAEGER_URL]', + + ADD_ENTITY: '[data-id=add-entity]', + ADD_SOURCE: '[data-id=add-source]', + ADD_DESTINATION: '[data-id=add-destination]', + ADD_ACTION: '[data-id=add-action]', + ADD_INSTRUMENTATION_RULE: '[data-id=add-rule]', + + MODAL: '[data-id=modal]', + MODAL_ADD_SOURCE: '[data-id=modal-Add-Source]', + MODAL_ADD_DESTINATION: '[data-id=modal-Add-Destination]', + MODAL_ADD_ACTION: '[data-id=modal-Add-Action]', + MODAL_ADD_INSTRUMENTATION_RULE: '[data-id=modal-Add-Instrumentation-Rule]', + + DRAWER: '[data-id=drawer]', + DRAWER_EDIT: '[data-id=drawer-edit]', + DRAWER_SAVE: '[data-id=drawer-save]', + DRAWER_CLOSE: '[data-id=drawer-close]', + DRAWER_DELETE: '[data-id=drawer-delete]', + APPROVE: '[data-id=approve]', + DENY: '[data-id=deny]', + + SOURCE_NODE_HEADER: '[data-id=source-header]', + SOURCE_NODE: '[data-id=source-1]', + DESTINATION_NODE: '[data-id=destination-0]', + ACTION_NODE: '[data-id=action-0]', + INSTRUMENTATION_RULE_NODE: '[data-id=rule-0]', + + ACTION_DROPDOWN_OPTION: '[data-id=option-pii-masking]', + MULTI_SOURCE_CONTROL: '[data-id=multi-source-control]', + + TITLE: '[data-id=title]', + SOURCE_TITLE: '[data-id=sourceName]', + CHECKBOX: '[data-id=checkbox]', +}; + +export const BUTTONS = { + NEXT: 'NEXT', + DONE: 'DONE', + ADD_DESTINATION: 'ADD DESTINATION', + UNINSTRUMENT: 'Uninstrument', +}; + +export const INPUTS = { + ACTION_DROPDOWN: 'Type to search...', +}; + +const CYPRESS_TEST = 'Cypress Test'; + +export const TEXTS = { + UPDATED_NAME: CYPRESS_TEST, + + SOURCE_WARN_MODAL_TITLE: 'Uninstrument 5 sources', + SOURCE_WARN_MODAL_NOTE: "You're about to uninstrument the last source", + DESTINATION_WARN_MODAL_TITLE: `Delete destination (${CYPRESS_TEST})`, + DESTINATION_WARN_MODAL_NOTE: "You're about to delete the last destination", + ACTION_WARN_MODAL_TITLE: `Delete action (${CYPRESS_TEST})`, + INSTRUMENTATION_RULE_WARN_MODAL_TITLE: `Delete rule (${CYPRESS_TEST})`, + + NO_RESOURCES: (namespace: string) => `No resources found in ${namespace} namespace.`, +}; diff --git a/frontend/webapp/cypress/e2e/01-connection.cy.ts b/frontend/webapp/cypress/e2e/01-connection.cy.ts index 8b92877ea..fd20e5af0 100644 --- a/frontend/webapp/cypress/e2e/01-connection.cy.ts +++ b/frontend/webapp/cypress/e2e/01-connection.cy.ts @@ -1,10 +1,10 @@ -import { ROUTES } from '../../utils/constants/routes'; +import { ROUTES } from '../constants'; describe('Root Connection', () => { - it('Should fetch a config with GraphQL. A redirect of any kind confirms Frontend + Backend connections.', () => { - cy.intercept('/graphql').as('gql'); - cy.visit('/'); + beforeEach(() => cy.intercept('/graphql').as('gql')); + it('Should fetch a config with GraphQL. A redirect of any kind confirms Frontend + Backend connections.', () => { + cy.visit(ROUTES.ROOT); cy.wait('@gql').then(() => { cy.location().should((loc) => { // If GraphQL failed to fetch the config, the app will remain on "/", thereby failing the test. diff --git a/frontend/webapp/cypress/e2e/02-onboarding.cy.ts b/frontend/webapp/cypress/e2e/02-onboarding.cy.ts index 783e7ee8f..b4604b567 100644 --- a/frontend/webapp/cypress/e2e/02-onboarding.cy.ts +++ b/frontend/webapp/cypress/e2e/02-onboarding.cy.ts @@ -1,39 +1,29 @@ -import { ROUTES } from '../../utils/constants/routes'; +import { BUTTONS, DATA_IDS, ROUTES, SELECTED_ENTITIES } from '../constants'; describe('Onboarding', () => { - beforeEach(() => { - cy.intercept('/graphql').as('gql'); - }); + beforeEach(() => cy.intercept('/graphql').as('gql')); - it('Should contain at least a "default" namespace', () => { + it('Should contain a "default" namespace', () => { cy.visit(ROUTES.CHOOSE_SOURCES); cy.wait('@gql').then(() => { - cy.get('[data-id=namespace-default]').contains('default').should('exist'); - }); - }); - - it('Should contain at least a "Jaeger" destination', () => { - cy.visit(ROUTES.CHOOSE_DESTINATION); - cy.contains('button', 'ADD DESTINATION').click(); - cy.wait('@gql').then(() => { - cy.get('[data-id=destination-jaeger]').contains('Jaeger').should('exist'); + cy.get(DATA_IDS.SELECT_NAMESPACE).contains(SELECTED_ENTITIES.NAMESPACE).should('exist'); }); }); - it('Should autocomplete the "Jaeger" destination', () => { + it('Should contain a "Jaeger" destination, and it should be autocompleted', () => { cy.visit(ROUTES.CHOOSE_DESTINATION); - cy.contains('button', 'ADD DESTINATION').click(); + cy.contains('button', BUTTONS.ADD_DESTINATION).click(); cy.wait('@gql').then(() => { - cy.get('[data-id=destination-jaeger]').contains('Jaeger').click(); - expect('[data-id=JAEGER_URL]').to.not.be.empty; + cy.get(DATA_IDS.SELECT_DESTINATION).contains(SELECTED_ENTITIES.DESTINATION).should('exist').click(); + expect(DATA_IDS.SELECT_DESTINATION_AUTOFILL_FIELD).to.not.be.empty; }); }); - it('Should allow the user to pass every step, and end-up on the "Overview" page.', () => { + it('Should allow the user to pass every step, and end-up on the "overview" page.', () => { cy.visit(ROUTES.CHOOSE_SOURCES); - cy.contains('button', 'NEXT').click(); + cy.contains('button', BUTTONS.NEXT).click(); cy.location('pathname').should('eq', ROUTES.CHOOSE_DESTINATION); - cy.contains('button', 'DONE').click(); + cy.contains('button', BUTTONS.DONE).click(); cy.location('pathname').should('eq', ROUTES.OVERVIEW); }); }); diff --git a/frontend/webapp/cypress/e2e/03-sources.cy.ts b/frontend/webapp/cypress/e2e/03-sources.cy.ts index 143553653..abc316cd7 100644 --- a/frontend/webapp/cypress/e2e/03-sources.cy.ts +++ b/frontend/webapp/cypress/e2e/03-sources.cy.ts @@ -1,45 +1,31 @@ -import { ROUTES } from '../../utils/constants/routes'; +import { getCrdById, getCrdIds, updateEntity } from '../functions'; +import { BUTTONS, CRD_IDS, CRD_NAMES, DATA_IDS, NAMESPACES, ROUTES, SELECTED_ENTITIES, TEXTS } from '../constants'; // The number of CRDs that exist in the cluster before running any tests should be 0. // Tests will fail if you have existing CRDs in the cluster. // If you have to run tests locally, make sure to clean up the cluster before running the tests. -describe('Sources CRUD', () => { - const namespace = 'default'; - const crdName = 'instrumentationconfigs.odigos.io'; - const noResourcesFound = `No resources found in ${namespace} namespace.`; +const namespace = NAMESPACES.DEFAULT; +const crdName = CRD_NAMES.SOURCE; - beforeEach(() => { - cy.intercept('/graphql').as('gql'); - }); +describe('Sources CRUD', () => { + beforeEach(() => cy.intercept('/graphql').as('gql')); it('Should create a CRD in the cluster', () => { cy.visit(ROUTES.OVERVIEW); - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListBefore) => { - expect(crdListBefore.stderr).to.eq(noResourcesFound); - expect(crdListBefore.stdout).to.eq(''); - - const crdIdsBefore = crdListBefore.stdout.split('\n').filter((str) => !!str); - expect(crdIdsBefore.length).to.eq(0); - - cy.get('[data-id=add-entity]').click(); - cy.get('[data-id=add-source]').click(); - cy.get('[data-id=modal-Add-Source]').should('exist'); - cy.get('[data-id=namespace-default]').find('[data-id=checkbox]').click(); + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }, () => { + cy.get(DATA_IDS.ADD_ENTITY).click(); + cy.get(DATA_IDS.ADD_SOURCE).click(); + cy.get(DATA_IDS.MODAL_ADD_SOURCE).should('exist'); + cy.get(DATA_IDS.SELECT_NAMESPACE).find(DATA_IDS.CHECKBOX).click(); // Wait for 3 seconds to allow the namespace & it's resources to be loaded into the UI cy.wait(3000).then(() => { - cy.get('button').contains('DONE').click(); + cy.contains('button', BUTTONS.DONE).click(); cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListAfter) => { - expect(crdListAfter.stderr).to.eq(''); - expect(crdListAfter.stdout).to.not.be.empty; - - const crdIdsAfter = crdListAfter.stdout.split('\n').filter((str) => !!str); - expect(crdIdsAfter.length).to.eq(5); - }); + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 5 }); }); }); }); @@ -48,58 +34,36 @@ describe('Sources CRUD', () => { it('Should update the CRD in the cluster', () => { cy.visit(ROUTES.OVERVIEW); - const node = cy.contains('[data-id=source-1]', 'frontend'); - expect(node).to.exist; - node.click(); - - cy.get('[data-id=drawer]').should('exist'); - cy.get('button[data-id=drawer-edit]').click(); - cy.get('input[data-id=sourceName]').clear().type('Cypress Test'); - cy.get('button[data-id=drawer-save]').click(); - cy.get('button[data-id=drawer-close]').click(); - - cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { - expect(crdList.stderr).to.eq(''); - expect(crdList.stdout).to.not.be.empty; - - const crdIds = crdList.stdout.split('\n').filter((str) => !!str); - const crdId = 'deployment-frontend'; - expect(crdIds.length).to.eq(5); - expect(crdIds).includes(crdId); - - cy.exec(`kubectl get ${crdName} ${crdId} -n ${namespace} -o json`).then((crd) => { - expect(crd.stderr).to.eq(''); - expect(crd.stdout).to.not.be.empty; - - const parsed = JSON.parse(crd.stdout); - const { spec } = parsed?.items?.[0] || parsed || {}; - - expect(spec).to.not.be.empty; - expect(spec.serviceName).to.eq('Cypress Test'); + updateEntity( + { + nodeId: DATA_IDS.SOURCE_NODE, + nodeContains: SELECTED_ENTITIES.SOURCE, + fieldKey: DATA_IDS.SOURCE_TITLE, + fieldValue: TEXTS.UPDATED_NAME, + }, + () => { + cy.wait('@gql').then(() => { + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 5 }, (crdIds) => { + const crdId = CRD_IDS.SOURCE; + expect(crdIds).includes(crdId); + getCrdById({ namespace, crdName, crdId, expectedError: '', expectedKey: 'serviceName', expectedValue: TEXTS.UPDATED_NAME }); + }); }); - }); - }); + }, + ); }); it('Should delete the CRD from the cluster', () => { cy.visit(ROUTES.OVERVIEW); - cy.get('[data-id=source-header]').find('[data-id=checkbox]').click(); - cy.get('[data-id=multi-source-control]').should('exist'); - cy.get('[data-id=multi-source-control]').find('button').contains('Uninstrument').click(); - cy.get('[data-id=modal]').contains('Uninstrument 5 sources').should('exist'); - cy.get('[data-id=modal]').contains("You're about to uninstrument the last source").should('exist'); - cy.get('button[data-id=approve]').click(); + cy.get(DATA_IDS.SOURCE_NODE_HEADER).find(DATA_IDS.CHECKBOX).click(); + cy.get(DATA_IDS.MULTI_SOURCE_CONTROL).should('exist').find('button').contains(BUTTONS.UNINSTRUMENT).click(); + cy.get(DATA_IDS.MODAL).contains(TEXTS.SOURCE_WARN_MODAL_TITLE).should('exist'); + cy.get(DATA_IDS.MODAL).contains(TEXTS.SOURCE_WARN_MODAL_NOTE).should('exist'); + cy.get(DATA_IDS.APPROVE).click(); cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { - expect(crdList.stderr).to.eq(noResourcesFound); - expect(crdList.stdout).to.eq(''); - - const crdIds = crdList.stdout.split('\n').filter((str) => !!str); - expect(crdIds.length).to.eq(0); - }); + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }); }); }); }); diff --git a/frontend/webapp/cypress/e2e/04-destinations.cy.ts b/frontend/webapp/cypress/e2e/04-destinations.cy.ts index 303ce0462..a3f1e427a 100644 --- a/frontend/webapp/cypress/e2e/04-destinations.cy.ts +++ b/frontend/webapp/cypress/e2e/04-destinations.cy.ts @@ -1,46 +1,29 @@ -import { ROUTES } from '../../utils/constants/routes'; +import { deleteEntity, getCrdById, getCrdIds, updateEntity } from '../functions'; +import { BUTTONS, CRD_NAMES, DATA_IDS, NAMESPACES, ROUTES, SELECTED_ENTITIES, TEXTS } from '../constants'; // The number of CRDs that exist in the cluster before running any tests should be 0. // Tests will fail if you have existing CRDs in the cluster. // If you have to run tests locally, make sure to clean up the cluster before running the tests. -describe('Destinations CRUD', () => { - const namespace = 'odigos-system'; - const crdName = 'destinations.odigos.io'; - const noResourcesFound = `No resources found in ${namespace} namespace.`; +const namespace = NAMESPACES.ODIGOS_SYSTEM; +const crdName = CRD_NAMES.DESTINATION; - beforeEach(() => { - cy.intercept('/graphql').as('gql'); - }); +describe('Destinations CRUD', () => { + beforeEach(() => cy.intercept('/graphql').as('gql')); it('Should create a CRD in the cluster', () => { cy.visit(ROUTES.OVERVIEW); - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListBefore) => { - expect(crdListBefore.stderr).to.eq(noResourcesFound); - expect(crdListBefore.stdout).to.eq(''); - - const crdIdsBefore = crdListBefore.stdout.split('\n').filter((str) => !!str); - expect(crdIdsBefore.length).to.eq(0); - - cy.get('[data-id=add-entity]').click(); - cy.get('[data-id=add-destination]').click(); - cy.get('[data-id=modal-Add-Destination]').should('exist'); + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }, () => { + cy.get(DATA_IDS.ADD_ENTITY).click(); + cy.get(DATA_IDS.ADD_DESTINATION).click(); + cy.get(DATA_IDS.MODAL_ADD_DESTINATION).should('exist'); + cy.get(DATA_IDS.SELECT_DESTINATION).contains(SELECTED_ENTITIES.DESTINATION).click(); + expect(DATA_IDS.SELECT_DESTINATION_AUTOFILL_FIELD).to.not.be.empty; + cy.get('button').contains(BUTTONS.DONE).click(); cy.wait('@gql').then(() => { - cy.get('[data-id=destination-jaeger]').contains('Jaeger').click(); - expect('[data-id=JAEGER_URL]').to.not.be.empty; - cy.get('button').contains('DONE').click(); - - cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListAfter) => { - expect(crdListAfter.stderr).to.eq(''); - expect(crdListAfter.stdout).to.not.be.empty; - - const crdIdsAfter = crdListAfter.stdout.split('\n').filter((str) => !!str); - expect(crdIdsAfter.length).to.eq(1); - }); - }); + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 1 }); }); }); }); @@ -48,60 +31,39 @@ describe('Destinations CRUD', () => { it('Should update the CRD in the cluster', () => { cy.visit(ROUTES.OVERVIEW); - const node = cy.contains('[data-id=destination-0]', 'Jaeger'); - expect(node).to.exist; - node.click(); - - cy.get('[data-id=drawer]').should('exist'); - cy.get('button[data-id=drawer-edit]').click(); - cy.get('input[data-id=title]').clear().type('Cypress Test'); - cy.get('button[data-id=drawer-save]').click(); - cy.get('button[data-id=drawer-close]').click(); - - cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { - expect(crdList.stderr).to.eq(''); - expect(crdList.stdout).to.not.be.empty; - - const crdIds = crdList.stdout.split('\n').filter((str) => !!str); - expect(crdIds.length).to.eq(1); - - cy.exec(`kubectl get ${crdName} ${crdIds[0]} -n ${namespace} -o json`).then((crd) => { - expect(crd.stderr).to.eq(''); - expect(crd.stdout).to.not.be.empty; - - const parsed = JSON.parse(crd.stdout); - const { spec } = parsed?.items?.[0] || parsed || {}; - - expect(spec).to.not.be.empty; - expect(spec.destinationName).to.eq('Cypress Test'); + updateEntity( + { + nodeId: DATA_IDS.DESTINATION_NODE, + nodeContains: SELECTED_ENTITIES.DESTINATION, + fieldKey: DATA_IDS.TITLE, + fieldValue: TEXTS.UPDATED_NAME, + }, + () => { + cy.wait('@gql').then(() => { + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 1 }, (crdIds) => { + const crdId = crdIds[0]; + getCrdById({ namespace, crdName, crdId, expectedError: '', expectedKey: 'destinationName', expectedValue: TEXTS.UPDATED_NAME }); + }); }); - }); - }); + }, + ); }); it('Should delete the CRD from the cluster', () => { cy.visit(ROUTES.OVERVIEW); - const node = cy.contains('[data-id=destination-0]', 'Jaeger'); - expect(node).to.exist; - node.click(); - - cy.get('[data-id=drawer]').should('exist'); - cy.get('button[data-id=drawer-edit]').click(); - cy.get('button[data-id=drawer-delete]').click(); - cy.get('[data-id=modal]').contains('Delete destination').should('exist'); - cy.get('[data-id=modal]').contains("You're about to delete the last destination").should('exist'); - cy.get('button[data-id=approve]').click(); - - cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { - expect(crdList.stderr).to.eq(noResourcesFound); - expect(crdList.stdout).to.eq(''); - - const crdIds = crdList.stdout.split('\n').filter((str) => !!str); - expect(crdIds.length).to.eq(0); - }); - }); + deleteEntity( + { + nodeId: DATA_IDS.DESTINATION_NODE, + nodeContains: SELECTED_ENTITIES.DESTINATION, + warnModalTitle: TEXTS.DESTINATION_WARN_MODAL_TITLE, + warnModalNote: TEXTS.DESTINATION_WARN_MODAL_NOTE, + }, + () => { + cy.wait('@gql').then(() => { + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }); + }); + }, + ); }); }); diff --git a/frontend/webapp/cypress/e2e/05-actions.cy.ts b/frontend/webapp/cypress/e2e/05-actions.cy.ts index 551c4c182..ff892921b 100644 --- a/frontend/webapp/cypress/e2e/05-actions.cy.ts +++ b/frontend/webapp/cypress/e2e/05-actions.cy.ts @@ -1,43 +1,29 @@ -import { ROUTES } from '../../utils/constants/routes'; +import { deleteEntity, getCrdById, getCrdIds, updateEntity } from '../functions'; +import { BUTTONS, CRD_NAMES, DATA_IDS, INPUTS, NAMESPACES, ROUTES, SELECTED_ENTITIES, TEXTS } from '../constants'; // The number of CRDs that exist in the cluster before running any tests should be 0. // Tests will fail if you have existing CRDs in the cluster. // If you have to run tests locally, make sure to clean up the cluster before running the tests. -describe('Actions CRUD', () => { - const namespace = 'odigos-system'; - const crdName = 'piimaskings.actions.odigos.io'; - const noResourcesFound = `No resources found in ${namespace} namespace.`; +const namespace = NAMESPACES.ODIGOS_SYSTEM; +const crdName = CRD_NAMES.ACTION; - beforeEach(() => { - cy.intercept('/graphql').as('gql'); - }); +describe('Actions CRUD', () => { + beforeEach(() => cy.intercept('/graphql').as('gql')); it('Should create a CRD in the cluster', () => { cy.visit(ROUTES.OVERVIEW); - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListBefore) => { - expect(crdListBefore.stderr).to.eq(noResourcesFound); - expect(crdListBefore.stdout).to.eq(''); - - const crdIdsBefore = crdListBefore.stdout.split('\n').filter((str) => !!str); - expect(crdIdsBefore.length).to.eq(0); - - cy.get('[data-id=add-entity]').click(); - cy.get('[data-id=add-action]').click(); - cy.get('[data-id=modal-Add-Action]').should('exist'); - cy.get('[data-id=modal-Add-Action]').find('input').should('have.attr', 'placeholder', 'Type to search...').click(); - cy.get('[data-id=option-pii-masking]').click(); - cy.get('button').contains('DONE').click(); + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }, () => { + cy.get(DATA_IDS.ADD_ENTITY).click(); + cy.get(DATA_IDS.ADD_ACTION).click(); + cy.get(DATA_IDS.MODAL_ADD_ACTION).should('exist'); + cy.get(DATA_IDS.MODAL_ADD_ACTION).find('input').should('have.attr', 'placeholder', INPUTS.ACTION_DROPDOWN).click(); + cy.get(DATA_IDS.ACTION_DROPDOWN_OPTION).click(); + cy.get('button').contains(BUTTONS.DONE).click(); cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListAfter) => { - expect(crdListAfter.stderr).to.eq(''); - expect(crdListAfter.stdout).to.not.be.empty; - - const crdIdsAfter = crdListAfter.stdout.split('\n').filter((str) => !!str); - expect(crdIdsAfter.length).to.eq(1); - }); + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 1 }); }); }); }); @@ -45,59 +31,38 @@ describe('Actions CRUD', () => { it('Should update the CRD in the cluster', () => { cy.visit(ROUTES.OVERVIEW); - const node = cy.contains('[data-id=action-0]', 'PiiMasking'); - expect(node).to.exist; - node.click(); - - cy.get('[data-id=drawer]').should('exist'); - cy.get('button[data-id=drawer-edit]').click(); - cy.get('input[data-id=title]').clear().type('Cypress Test'); - cy.get('button[data-id=drawer-save]').click(); - cy.get('button[data-id=drawer-close]').click(); - - cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { - expect(crdList.stderr).to.eq(''); - expect(crdList.stdout).to.not.be.empty; - - const crdIds = crdList.stdout.split('\n').filter((str) => !!str); - expect(crdIds.length).to.eq(1); - - cy.exec(`kubectl get ${crdName} ${crdIds[0]} -n ${namespace} -o json`).then((crd) => { - expect(crd.stderr).to.eq(''); - expect(crd.stdout).to.not.be.empty; - - const parsed = JSON.parse(crd.stdout); - const { spec } = parsed?.items?.[0] || parsed || {}; - - expect(spec).to.not.be.empty; - expect(spec.actionName).to.eq('Cypress Test'); + updateEntity( + { + nodeId: DATA_IDS.ACTION_NODE, + nodeContains: SELECTED_ENTITIES.ACTION, + fieldKey: DATA_IDS.TITLE, + fieldValue: TEXTS.UPDATED_NAME, + }, + () => { + cy.wait('@gql').then(() => { + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 1 }, (crdIds) => { + const crdId = crdIds[0]; + getCrdById({ namespace, crdName, crdId, expectedError: '', expectedKey: 'actionName', expectedValue: TEXTS.UPDATED_NAME }); + }); }); - }); - }); + }, + ); }); it('Should delete the CRD from the cluster', () => { cy.visit(ROUTES.OVERVIEW); - const node = cy.contains('[data-id=action-0]', 'PiiMasking'); - expect(node).to.exist; - node.click(); - - cy.get('[data-id=drawer]').should('exist'); - cy.get('button[data-id=drawer-edit]').click(); - cy.get('button[data-id=drawer-delete]').click(); - cy.get('[data-id=modal]').contains('Delete action').should('exist'); - cy.get('button[data-id=approve]').click(); - - cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { - expect(crdList.stderr).to.eq(noResourcesFound); - expect(crdList.stdout).to.eq(''); - - const crdIds = crdList.stdout.split('\n').filter((str) => !!str); - expect(crdIds.length).to.eq(0); - }); - }); + deleteEntity( + { + nodeId: DATA_IDS.ACTION_NODE, + nodeContains: SELECTED_ENTITIES.ACTION, + warnModalTitle: TEXTS.ACTION_WARN_MODAL_TITLE, + }, + () => { + cy.wait('@gql').then(() => { + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }); + }); + }, + ); }); }); diff --git a/frontend/webapp/cypress/e2e/06-rules.cy.ts b/frontend/webapp/cypress/e2e/06-rules.cy.ts index 84e4c035b..566d5e153 100644 --- a/frontend/webapp/cypress/e2e/06-rules.cy.ts +++ b/frontend/webapp/cypress/e2e/06-rules.cy.ts @@ -1,41 +1,27 @@ -import { ROUTES } from '../../utils/constants/routes'; +import { deleteEntity, getCrdById, getCrdIds, updateEntity } from '../functions'; +import { BUTTONS, CRD_NAMES, DATA_IDS, NAMESPACES, ROUTES, SELECTED_ENTITIES, TEXTS } from '../constants'; // The number of CRDs that exist in the cluster before running any tests should be 0. // Tests will fail if you have existing CRDs in the cluster. // If you have to run tests locally, make sure to clean up the cluster before running the tests. -describe('Instrumentation Rules CRUD', () => { - const namespace = 'odigos-system'; - const crdName = 'instrumentationrules.odigos.io'; - const noResourcesFound = `No resources found in ${namespace} namespace.`; +const namespace = NAMESPACES.ODIGOS_SYSTEM; +const crdName = CRD_NAMES.INSTRUMENTATION_RULE; - beforeEach(() => { - cy.intercept('/graphql').as('gql'); - }); +describe('Instrumentation Rules CRUD', () => { + beforeEach(() => cy.intercept('/graphql').as('gql')); it('Should create a CRD in the cluster', () => { cy.visit(ROUTES.OVERVIEW); - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListBefore) => { - expect(crdListBefore.stderr).to.eq(noResourcesFound); - expect(crdListBefore.stdout).to.eq(''); - - const crdIdsBefore = crdListBefore.stdout.split('\n').filter((str) => !!str); - expect(crdIdsBefore.length).to.eq(0); - - cy.get('[data-id=add-entity]').click(); - cy.get('[data-id=add-rule]').click(); - cy.get('[data-id=modal-Add-Instrumentation-Rule]').should('exist'); - cy.get('button').contains('DONE').click(); + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }, () => { + cy.get(DATA_IDS.ADD_ENTITY).click(); + cy.get(DATA_IDS.ADD_INSTRUMENTATION_RULE).click(); + cy.get(DATA_IDS.MODAL_ADD_INSTRUMENTATION_RULE).should('exist'); + cy.get('button').contains(BUTTONS.DONE).click(); cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdListAfter) => { - expect(crdListAfter.stderr).to.eq(''); - expect(crdListAfter.stdout).to.not.be.empty; - - const crdIdsAfter = crdListAfter.stdout.split('\n').filter((str) => !!str); - expect(crdIdsAfter.length).to.eq(1); - }); + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 1 }); }); }); }); @@ -43,59 +29,38 @@ describe('Instrumentation Rules CRUD', () => { it('Should update the CRD in the cluster', () => { cy.visit(ROUTES.OVERVIEW); - const node = cy.contains('[data-id=rule-0]', 'PayloadCollection'); - expect(node).to.exist; - node.click(); - - cy.get('[data-id=drawer]').should('exist'); - cy.get('button[data-id=drawer-edit]').click(); - cy.get('input[data-id=title]').clear().type('Cypress Test'); - cy.get('button[data-id=drawer-save]').click(); - cy.get('button[data-id=drawer-close]').click(); - - cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { - expect(crdList.stderr).to.eq(''); - expect(crdList.stdout).to.not.be.empty; - - const crdIds = crdList.stdout.split('\n').filter((str) => !!str); - expect(crdIds.length).to.eq(1); - - cy.exec(`kubectl get ${crdName} ${crdIds[0]} -n ${namespace} -o json`).then((crd) => { - expect(crd.stderr).to.eq(''); - expect(crd.stdout).to.not.be.empty; - - const parsed = JSON.parse(crd.stdout); - const { spec } = parsed?.items?.[0] || parsed || {}; - - expect(spec).to.not.be.empty; - expect(spec.ruleName).to.eq('Cypress Test'); + updateEntity( + { + nodeId: DATA_IDS.INSTRUMENTATION_RULE_NODE, + nodeContains: SELECTED_ENTITIES.INSTRUMENTATION_RULE, + fieldKey: DATA_IDS.TITLE, + fieldValue: TEXTS.UPDATED_NAME, + }, + () => { + cy.wait('@gql').then(() => { + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 1 }, (crdIds) => { + const crdId = crdIds[0]; + getCrdById({ namespace, crdName, crdId, expectedError: '', expectedKey: 'ruleName', expectedValue: TEXTS.UPDATED_NAME }); + }); }); - }); - }); + }, + ); }); it('Should delete the CRD from the cluster', () => { cy.visit(ROUTES.OVERVIEW); - const node = cy.contains('[data-id=rule-0]', 'PayloadCollection'); - expect(node).to.exist; - node.click(); - - cy.get('[data-id=drawer]').should('exist'); - cy.get('button[data-id=drawer-edit]').click(); - cy.get('button[data-id=drawer-delete]').click(); - cy.get('[data-id=modal]').contains('Delete rule').should('exist'); - cy.get('button[data-id=approve]').click(); - - cy.wait('@gql').then(() => { - cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then((crdList) => { - expect(crdList.stderr).to.eq(noResourcesFound); - expect(crdList.stdout).to.eq(''); - - const crdIds = crdList.stdout.split('\n').filter((str) => !!str); - expect(crdIds.length).to.eq(0); - }); - }); + deleteEntity( + { + nodeId: DATA_IDS.INSTRUMENTATION_RULE_NODE, + nodeContains: SELECTED_ENTITIES.INSTRUMENTATION_RULE, + warnModalTitle: TEXTS.INSTRUMENTATION_RULE_WARN_MODAL_TITLE, + }, + () => { + cy.wait('@gql').then(() => { + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }); + }); + }, + ); }); }); diff --git a/frontend/webapp/cypress/functions/index.ts b/frontend/webapp/cypress/functions/index.ts new file mode 100644 index 000000000..15f4e3c18 --- /dev/null +++ b/frontend/webapp/cypress/functions/index.ts @@ -0,0 +1,93 @@ +import { DATA_IDS } from '../constants'; + +interface GetCrdIdsOptions { + namespace: string; + crdName: string; + expectedError: string; + expectedLength: number; +} + +export const getCrdIds = ({ namespace, crdName, expectedError, expectedLength }: GetCrdIdsOptions, callback?: (crdIds: string[]) => void) => { + cy.exec(`kubectl get ${crdName} -n ${namespace} | awk 'NR>1 {print $1}'`).then(({ stderr, stdout }) => { + expect(stderr).to.eq(expectedError); + + if (!!expectedError) { + expect(stdout).to.eq(''); + } else { + expect(stdout).to.not.be.empty; + } + + const crdIds = stdout.split('\n').filter((str) => !!str); + expect(crdIds.length).to.eq(expectedLength); + + if (!!callback) callback(crdIds); + }); +}; + +interface GetCrdByIdOptions { + namespace: string; + crdName: string; + crdId: string; + expectedError: string; + expectedKey: string; + expectedValue: string; +} + +export const getCrdById = ({ namespace, crdName, crdId, expectedError, expectedKey, expectedValue }: GetCrdByIdOptions, callback?: () => void) => { + cy.exec(`kubectl get ${crdName} ${crdId} -n ${namespace} -o json`).then(({ stderr, stdout }) => { + expect(stderr).to.eq(expectedError); + + if (!!expectedError) { + expect(stdout).to.eq(''); + } else { + expect(stdout).to.not.be.empty; + } + + const parsed = JSON.parse(stdout); + const { spec } = parsed?.items?.[0] || parsed || {}; + + expect(spec).to.not.be.empty; + expect(spec[expectedKey]).to.eq(expectedValue); + + if (!!callback) callback(); + }); +}; + +interface UpdateEntityOptions { + nodeId: string; + nodeContains: string; + fieldKey: string; + fieldValue: string; +} + +export const updateEntity = ({ nodeId, nodeContains, fieldKey, fieldValue }: UpdateEntityOptions, callback?: () => void) => { + cy.contains(nodeId, nodeContains).should('exist').click(); + cy.get(DATA_IDS.DRAWER).should('exist'); + cy.get(DATA_IDS.DRAWER_EDIT).click(); + cy.get(fieldKey).clear().type(fieldValue); + cy.get(DATA_IDS.DRAWER_SAVE).click(); + cy.get(DATA_IDS.DRAWER_CLOSE).click(); + + if (!!callback) callback(); +}; + +interface DeleteEntityOptions { + nodeId: string; + nodeContains: string; + warnModalTitle?: string; + warnModalNote?: string; +} + +export const deleteEntity = ({ nodeId, nodeContains, warnModalTitle, warnModalNote }: DeleteEntityOptions, callback?: () => void) => { + cy.contains(nodeId, nodeContains).should('exist').click(); + cy.get(DATA_IDS.DRAWER).should('exist'); + cy.get(DATA_IDS.DRAWER_EDIT).click(); + cy.get(DATA_IDS.DRAWER_DELETE).click(); + + if (!!warnModalTitle) cy.get(DATA_IDS.MODAL).contains(warnModalTitle).should('exist'); + if (!!warnModalNote) cy.get(DATA_IDS.MODAL).contains(warnModalNote).should('exist'); + + cy.get(DATA_IDS.APPROVE).click(); + + if (!!callback) callback(); +}; diff --git a/frontend/webapp/utils/constants/routes.tsx b/frontend/webapp/utils/constants/routes.tsx index 707d050e0..c5e8c197c 100644 --- a/frontend/webapp/utils/constants/routes.tsx +++ b/frontend/webapp/utils/constants/routes.tsx @@ -1,4 +1,5 @@ export const ROUTES = { + ROOT: '/', CHOOSE_SOURCES: '/choose-sources', CHOOSE_DESTINATION: '/choose-destination', OVERVIEW: '/overview',