diff --git a/frontend/src/__tests__/cypress/cypress/fixtures/e2e/dataScienceProjects/testProjectWbPV.yaml b/frontend/src/__tests__/cypress/cypress/fixtures/e2e/dataScienceProjects/testProjectWbPV.yaml new file mode 100644 index 0000000000..ba8b0fb15e --- /dev/null +++ b/frontend/src/__tests__/cypress/cypress/fixtures/e2e/dataScienceProjects/testProjectWbPV.yaml @@ -0,0 +1,5 @@ +# workbenches.cy.ts Test Data # +NAMESPACE: 'dsp-wb-test' +PVC_NAME: 'pvc-test-name' +PVC_DISPLAY_NAME: 'pvc-test-display-name' +PVC_SIZE: '10' diff --git a/frontend/src/__tests__/cypress/cypress/fixtures/resources/yaml/persistentVolumeClaim.yaml b/frontend/src/__tests__/cypress/cypress/fixtures/resources/yaml/persistentVolumeClaim.yaml new file mode 100644 index 0000000000..e2bb24d6e2 --- /dev/null +++ b/frontend/src/__tests__/cypress/cypress/fixtures/resources/yaml/persistentVolumeClaim.yaml @@ -0,0 +1,22 @@ +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{PVC_NAME}} + namespace: {{NAMESPACE}} + labels: + opendatahub.io/dashboard: 'true' + annotations: + openshift.io/description: '' + openshift.io/display-name: {{PVC_DISPLAY_NAME}} + finalizers: + - kubernetes.io/pvc-protection +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{PVC_SIZE}}Gi + storageClassName: standard-csi + volumeMode: Filesystem +status: + phase: Pending diff --git a/frontend/src/__tests__/cypress/cypress/pages/workbench.ts b/frontend/src/__tests__/cypress/cypress/pages/workbench.ts index e6ff13c319..5fccd65fea 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/workbench.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/workbench.ts @@ -169,8 +169,12 @@ class NotebookRow extends TableRow { return this.find().findByTestId('notebook-route-link'); } - findHaveNotebookStatusText() { - return this.find().findByTestId('notebook-status-text'); + findHaveNotebookStatusText(timeout = 10000) { + return this.find().findByTestId('notebook-status-text', { timeout }); + } + + expectStatusLabelToBe(statusValue: string, timeout?: number) { + this.findHaveNotebookStatusText(timeout).should('have.text', statusValue); } findNotebookStart() { @@ -247,6 +251,10 @@ class CreateSpawnerPage { return new StorageTable(); } + getNameInput() { + return cy.findByTestId('workbench-name'); + } + private findPVSizeField() { return cy.findByTestId('create-new-storage-size'); } @@ -255,6 +263,10 @@ class CreateSpawnerPage { return cy.findByTestId('value-unit-select'); } + findExsistingPersistentStorageRadio() { + return cy.findByTestId('persistent-existing-storage-type-radio'); + } + selectExistingPersistentStorage(name: string) { cy.findByTestId('persistent-storage-group') .findByRole('button', { name: 'Typeahead menu toggle' }) diff --git a/frontend/src/__tests__/cypress/cypress/tests/e2e/dataScienceProjects/workbenches/workbenches.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/e2e/dataScienceProjects/workbenches/workbenches.cy.ts new file mode 100644 index 0000000000..7374f3110a --- /dev/null +++ b/frontend/src/__tests__/cypress/cypress/tests/e2e/dataScienceProjects/workbenches/workbenches.cy.ts @@ -0,0 +1,87 @@ +import type { PVCReplacements } from '~/__tests__/cypress/cypress/types'; +import { projectDetails, projectListPage } from '~/__tests__/cypress/cypress/pages/projects'; +import { workbenchPage, createSpawnerPage } from '~/__tests__/cypress/cypress/pages/workbench'; +import { clusterStorage } from '~/__tests__/cypress/cypress/pages/clusterStorage'; +import { HTPASSWD_CLUSTER_ADMIN_USER } from '~/__tests__/cypress/cypress/utils/e2eUsers'; +import { loadPVCFixture } from '~/__tests__/cypress/cypress/utils/dataLoader'; +import { createCleanProject } from '~/__tests__/cypress/cypress/utils/projectChecker'; +import { deleteOpenShiftProject } from '~/__tests__/cypress/cypress/utils/oc_commands/project'; +import { createPersistentVolumeClaim } from '~/__tests__/cypress/cypress/utils/oc_commands/presistentVolumeClaim'; + +describe('Workbench and PVSs tests', () => { + // let testData: PVCReplacements; + let projectName: string; + let PVCName: string; + let PVCDisplayName: string; + let PVCSize: string; + + before(() => { + return loadPVCFixture('e2e/dataScienceProjects/testProjectWbPV.yaml') + .then((fixtureData: PVCReplacements) => { + projectName = fixtureData.NAMESPACE; + PVCName = fixtureData.PVC_NAME; + PVCDisplayName = fixtureData.PVC_DISPLAY_NAME; + PVCSize = fixtureData.PVC_SIZE; + + if (!projectName) { + throw new Error('Project name is undefined or empty in the loaded fixture'); + } + cy.log(`Loaded project name: ${projectName}`); + return createCleanProject(projectName); + }) + .then(() => { + cy.log(`Project ${projectName} confirmed to be created and verified successfully`); + const pvcReplacements: PVCReplacements = { + NAMESPACE: projectName, + PVC_NAME: PVCName, + PVC_DISPLAY_NAME: PVCDisplayName, + PVC_SIZE: PVCSize, + }; + return createPersistentVolumeClaim(pvcReplacements); + }) + .then((commandResult) => { + cy.log(`Persistent Volume Claim created: ${JSON.stringify(commandResult)}`); + }); + }); + + after(() => { + // Delete provisioned Project + if (projectName) { + cy.log(`Deleting Project ${projectName} after the test has finished.`); + deleteOpenShiftProject(projectName); + } + }); + + it('Verify users can create a workbench and connect an existent PersistentVolume', () => { + const workbenchName = projectName.replace('dsp-', ''); + + // Authentication and navigation + cy.step('Log into the application'); + cy.visitWithLogin('/', HTPASSWD_CLUSTER_ADMIN_USER); + + cy.step(`Navigate to Workbenches tab of Project ${projectName}`); + projectListPage.navigate(); + projectListPage.filterProjectByName(projectName); + projectListPage.findProjectLink(projectName).click(); + projectDetails.findSectionTab('workbenches').click(); + + cy.step(`Create Workbench ${projectName} using storage ${PVCDisplayName}`); + workbenchPage.findCreateButton().click(); + createSpawnerPage.getNameInput().fill(workbenchName); + createSpawnerPage.findNotebookImage('s2i-minimal-notebook').click(); + createSpawnerPage.findExsistingPersistentStorageRadio().click(); + createSpawnerPage.selectExistingPersistentStorage(PVCDisplayName); + createSpawnerPage.findSubmitButton().click(); + + cy.step(`Wait for Workbench ${workbenchName} to display a "Running" status`); + const notebookRow = workbenchPage.getNotebookRow(workbenchName); + notebookRow.expectStatusLabelToBe('Running', 120000); + notebookRow.shouldHaveNotebookImageName('Minimal Python'); + notebookRow.shouldHaveContainerSize('Small'); + + cy.step(`Check the cluster storage ${PVCDisplayName} is now connected to ${workbenchName}`); + projectDetails.findSectionTab('cluster-storages').click(); + const csRow = clusterStorage.getClusterStorageRow(PVCDisplayName); + csRow.findConnectedWorkbenches().should('have.text', workbenchName); + }); +}); diff --git a/frontend/src/__tests__/cypress/cypress/types.ts b/frontend/src/__tests__/cypress/cypress/types.ts index 941a0e4c7b..e494a45e81 100644 --- a/frontend/src/__tests__/cypress/cypress/types.ts +++ b/frontend/src/__tests__/cypress/cypress/types.ts @@ -73,6 +73,13 @@ export type SCReplacements = { SC_IS_ENABLED: string; }; +export type PVCReplacements = { + NAMESPACE: string; + PVC_NAME: string; + PVC_DISPLAY_NAME: string; + PVC_SIZE: string; +}; + export type CommandLineResult = { code: number; stdout: string; diff --git a/frontend/src/__tests__/cypress/cypress/utils/dataLoader.ts b/frontend/src/__tests__/cypress/cypress/utils/dataLoader.ts index 666541b4ba..666a45c6a0 100644 --- a/frontend/src/__tests__/cypress/cypress/utils/dataLoader.ts +++ b/frontend/src/__tests__/cypress/cypress/utils/dataLoader.ts @@ -1,5 +1,9 @@ import yaml from 'js-yaml'; -import type { DataScienceProjectData, ResourcesData } from '~/__tests__/cypress/cypress/types'; +import type { + DataScienceProjectData, + PVCReplacements, + ResourcesData, +} from '~/__tests__/cypress/cypress/types'; // Load fixture function that returns DataScienceProjectData export const loadDSPFixture = (fixturePath: string): Cypress.Chainable => { @@ -16,3 +20,11 @@ export const loadResourcesFixture = (fixturePath: string): Cypress.Chainable => { + return cy.fixture(fixturePath, 'utf8').then((yamlContent: string) => { + const data = yaml.load(yamlContent) as PVCReplacements; + + return data; + }); +}; diff --git a/frontend/src/__tests__/cypress/cypress/utils/oc_commands/presistentVolumeClaim.ts b/frontend/src/__tests__/cypress/cypress/utils/oc_commands/presistentVolumeClaim.ts new file mode 100644 index 0000000000..7fe3d5f3c2 --- /dev/null +++ b/frontend/src/__tests__/cypress/cypress/utils/oc_commands/presistentVolumeClaim.ts @@ -0,0 +1,29 @@ +import type { PVCReplacements, CommandLineResult } from '~/__tests__/cypress/cypress/types'; +import { replacePlaceholdersInYaml } from '~/__tests__/cypress/cypress/utils/yaml_files'; +import { applyOpenShiftYaml } from './baseCommands'; + +/** + * Create an Persistent Volume Claim based on the PVCReplacements config + * @param persistentVolumeClaimReplacements Dictionary with the config values + * Dict Structure: + * export type PVCReplacements = { + * NAMESPACE: string; + * PVC_NAME: string; + * PVC_DISPLAY_NAME: string; + * PVC_SIZE: string; + * }; + * @param yamlFilePath + */ +export const createPersistentVolumeClaim = ( + persistentVolumeClaimReplacements: PVCReplacements, + yamlFilePath = 'resources/yaml/persistentVolumeClaim.yaml', +): Cypress.Chainable => { + return cy.fixture(yamlFilePath).then((yamlContent) => { + cy.log(yamlContent); + const modifiedYamlContent = replacePlaceholdersInYaml( + yamlContent, + persistentVolumeClaimReplacements, + ); + return applyOpenShiftYaml(modifiedYamlContent); + }); +};