Skip to content

Commit

Permalink
Cypress e2e Project Creation/Deletion (#3388)
Browse files Browse the repository at this point in the history
* Cypress e2e Project Creation/Deletion

* Fixed code coverage test issue

* Fixed linting

* Making changes to test case name and types

* Fixing linting again

* Changed types, split up the oc command (get, verify), added error handling and changed main test to use new split oc command

* Made changes to fix linting errors

* Made changes to folder structure and file names

* Introduced cypress-plugin-steps, removed custom step logic.

* Adding more relavant comment to test data file

* Fixed more linting issues, resolved merge conflicts and cleaned up some comments

* Recomitting plugin changes

* Fixed a trivial prettier linter issue

---------

Co-authored-by: Fede Alonso <fealonso@redhat.com>
  • Loading branch information
antowaddle and FedeAlonso authored Oct 31, 2024
1 parent 538a8ff commit a4ba326
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 217 deletions.
268 changes: 52 additions & 216 deletions frontend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"axios": "^1.6.4",
"classnames": "^2.2.6",
"compare-versions": "^3.6.0",
"cypress-plugin-steps": "^1.1.1",
"dompurify": "^2.2.6",
"google-protobuf": "^3.11.2",
"grpc-web": "^1.2.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# testProjectCreation.cy.ts Test Data #
dsProjectName: "Cypress Test Project 1"
dsProjectDescription: "Cypress Test project 1 description."
dsOCProjectName: "cypress-test-project-1"
16 changes: 16 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ class ProjectDetails {
return this.findKserveModelsTable().findByTestId(`metrics-link-${name}`);
}

verifyProjectName(project: string) {
return cy.get('[data-testid="app-page-title"]').should('contain.text', project);
}

verifyProjectDescription(description: string) {
return cy.findByText(description);
}

findActions() {
return cy.findByTestId('project-actions');
}

findDeleteProjectButton() {
return cy.findByTestId('delete-project-action').find('button');
}

getKserveTableRow(name: string) {
return new KserveTableRow(() =>
this.findKserveModelsTable()
Expand Down
1 change: 1 addition & 0 deletions frontend/src/__tests__/cypress/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'cypress-mochawesome-reporter/register';
import './commands';
import { asProjectAdminUser } from '~/__tests__/cypress/cypress/utils/mockUsers';
import { addCommands as webSocketsAddCommands } from './websockets';
import 'cypress-plugin-steps';

chai.use(chaiSubset);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import yaml from 'js-yaml';
import { ADMIN_USER } from '~/__tests__/cypress/cypress/utils/e2eUsers';
import {
projectListPage,
createProjectModal,
projectDetails,
} from '~/__tests__/cypress/cypress/pages/projects';
import {
verifyOpenShiftProjectExists,
deleteOpenShiftProject,
} from '~/__tests__/cypress/cypress/utils/oc_commands/project';
import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';
import type { DataScienceProjectData } from '~/__tests__/cypress/cypress/types';

describe('Verify Data Science Project - Creation and Deletion', () => {
let testData: DataScienceProjectData;

// Setup: Load test data and ensure clean state
before(() => {
return cy
.fixture('e2e/dataScienceProjects/dataScienceProject.yaml', 'utf8')
.then((yamlContent: string) => {
testData = yaml.load(yamlContent) as DataScienceProjectData;
const projectName = testData.dsOCProjectName;

if (!projectName) {
throw new Error('Project name is undefined or empty');
}

return verifyOpenShiftProjectExists(projectName);
})
.then((exists: boolean) => {
const projectName = testData.dsOCProjectName;
// Clean up existing project if it exists
if (exists) {
cy.log(`Project ${projectName} exists. Deleting before test.`);
return deleteOpenShiftProject(projectName);
}
cy.log(`Project ${projectName} does not exist. Proceeding with test.`);
// Return a resolved promise to ensure a value is always returned
return cy.wrap(null);
});
});

it('Create and Delete a Data Science Project in RHOAI', () => {
// Authentication and navigation
cy.step('Log into the application');
cy.visitWithLogin('/', ADMIN_USER);
projectListPage.navigate();

// Initiate project creation
cy.step('Open Create Data Science Project modal');
createProjectModal.shouldBeOpen(false);
projectListPage.findCreateProjectButton().click();

// Input project details
cy.step('Enter valid project information');
createProjectModal.k8sNameDescription.findDisplayNameInput().type(testData.dsProjectName);
createProjectModal.k8sNameDescription
.findDescriptionInput()
.type(testData.dsProjectDescription);

// Submit project creation
cy.step('Save the project');
createProjectModal.findSubmitButton().click();

// Verify project creation
cy.step(`Verify that the project ${testData.dsProjectName} has been created`);
cy.url().should('include', `/projects/${testData.dsOCProjectName}`);
projectDetails.verifyProjectName(testData.dsProjectName);
projectDetails.verifyProjectDescription(testData.dsProjectDescription);

// Initiate project deletion
cy.step('Deleting the project - clicking actions');
projectDetails.findActions().click();
projectDetails.findDeleteProjectAction().click();

// Confirm project deletion
cy.step('Entering project details for deletion');
deleteModal.shouldBeOpen();
deleteModal.findInput().type(testData.dsProjectName);
deleteModal.findSubmitButton().should('be.enabled').click();

// Verify project deletion
cy.step(`Verify that the project ${testData.dsProjectName} has been deleted`);
projectListPage.filterProjectByName(testData.dsProjectName);
projectListPage.findEmptyResults();
});
});
6 changes: 6 additions & 0 deletions frontend/src/__tests__/cypress/cypress/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export type TestConfig = {
S3: AWSS3Buckets;
};

export type DataScienceProjectData = {
dsProjectName: string;
dsProjectDescription: string;
dsOCProjectName: string;
};

export type NimServingResponse = {
body: {
body: ConfigMapKind | SecretKind;
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/__tests__/cypress/cypress/utils/errorHandling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Handles the result of an OpenShift (oc) command execution.
* @param result The result of the command execution.
* @throws Error if the command fails with an unexpected exit code.
*/
export function handleOCCommandResult(result: Cypress.Exec): void {
if (result.code !== 0 && result.code !== 1) {
// When some resources e.g. projects don't exist
cy.log(`ERROR: Command execution failed
stdout: ${result.stdout}
stderr: ${result.stderr}`);
throw new Error(`Command failed with code ${result.code}`);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CommandLineResult } from '~/__tests__/cypress/cypress/types';
import { handleOCCommandResult } from '~/__tests__/cypress/cypress/utils/errorHandling';

/**
* Create an OpenShift Project
Expand Down Expand Up @@ -71,3 +72,43 @@ export const addUserToProject = (
return result;
});
};

/**
* Get OpenShift Project
*
* @param projectName OpenShift Project name to get (in the format like 'cypress-test-project')
* @returns A Cypress chainable with the project information or null if not found
*/
export const getOpenShiftProject = (projectName: string): Cypress.Chainable<string | null> => {
if (!projectName || typeof projectName !== 'string') {
cy.log(`ERROR: Invalid project name provided: ${projectName}`);
throw new Error(`Invalid project name: ${projectName}`);
}
const checkCommand = `oc get project "${projectName}" -o name`;
cy.log(`Executing command: ${checkCommand}`);
return cy.exec(checkCommand, { failOnNonZeroExit: false }).then((result: Cypress.Exec) => {
cy.log(`Command result: ${JSON.stringify(result)}`);
// Use the utility function to handle command result
handleOCCommandResult(result);
// Use cy.wrap to ensure we're returning a Cypress chainable
return cy.wrap(result.code === 0 ? result.stdout.trim() : null);
});
};

/**
* Verify if an OpenShift Project exists
*
* @param projectName OpenShift Project name to verify (in the format like 'cypress-test-project')
* @returns A Cypress chainable boolean indicating whether the project exists (true) or not (false)
*/
export const verifyOpenShiftProjectExists = (projectName: string): Cypress.Chainable<boolean> => {
return getOpenShiftProject(projectName).then((projectInfo) => {
const projectExists = projectInfo === `project.project.openshift.io/${projectName}`;
if (projectExists) {
cy.log(`Project '${projectName}' exists.`);
} else {
cy.log(`Project '${projectName}' does not exist.`);
}
return cy.wrap(projectExists);
});
};
2 changes: 1 addition & 1 deletion frontend/src/__tests__/cypress/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"exclude": ["node_modules", "public", "coverage", ".nyc_output"],
"compilerOptions": {
"sourceMap": true,
"types": ["node", "cypress", "@testing-library/cypress", "cypress-axe"]
"types": ["node", "cypress", "@testing-library/cypress", "cypress-axe", "cypress-plugin-steps"]
}
}

0 comments on commit a4ba326

Please sign in to comment.