Skip to content

Commit

Permalink
Test: Import and run a Pipeline (#3028)
Browse files Browse the repository at this point in the history
* Provision project and data connection, clean up project

* fix temporal path

* add DSPA Secret provision

* Provision and clean-up working properly

* Import a pipeline from DSP modal

* Test running OK

* clean comments and disable the fetching of backend requests in live testing

* fix lintings

* Fixes to 2.12 compatibility

* Delete finalizers, generation and managedFields from yaml

* Update frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts

Co-authored-by: Christian Vogt <[email protected]>

* some fixes and run pipeline from actions

* delete wait when waiting run status

* fix oc commands

* delete login assertion

* stop waiting for project deletion

* AWS like ods-ci

* lint fixes

* modularize the oc calls

* lint fixes

* delete comment

* Use a dummy pipeline from our repo

* test: Add a dummy pipeline sample

* add dummy pipeline

* fix pipeline url

* filter the project before trying to open it in the DSP view

* fix data-testid for pipeline run status icon

* yml to yaml

* delete unused code

* filter project name properly

* Wait for status editable

* make display name not mandatory

* move filterProjectByName

* use proper types

* lint fixes

* add lint fixes

* Update frontend/src/__tests__/cypress/cypress/utils/oc_commands/project.ts

Co-authored-by: Christian Vogt <[email protected]>

* Update frontend/src/__tests__/cypress/cypress/utils/oc_commands/dspa.ts

Co-authored-by: Christian Vogt <[email protected]>

* delete displayName redundancy

* Update frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts

Co-authored-by: Christian Vogt <[email protected]>

* Update frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts

Co-authored-by: Christian Vogt <[email protected]>

* Update frontend/src/__tests__/cypress/cypress/pages/projects.ts

Co-authored-by: Christian Vogt <[email protected]>

* move uncaught exception to particular test

* delete the uncaught exception control as it's fixed

* Update frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts

Co-authored-by: Christian Vogt <[email protected]>

* Update frontend/src/__tests__/cypress/cypress/utils/oc_commands/baseCommands.ts

Co-authored-by: Christian Vogt <[email protected]>

* add log when deleting a project using oc

* add a helper which contains the test provisioning

* lint fixes

---------

Co-authored-by: Christian Vogt <[email protected]>
  • Loading branch information
FedeAlonso and christianvogt authored Sep 6, 2024
1 parent 575959e commit d786249
Show file tree
Hide file tree
Showing 22 changed files with 562 additions and 6 deletions.
2 changes: 2 additions & 0 deletions frontend/src/__tests__/cypress/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ cypress/downloads
coverage
results
.nyc_output
test-variables.yml
cypress/temp*.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
kind: Secret
apiVersion: v1
metadata:
name: aws-connection-ods-ci-ds-pipelines
namespace: {{NAMESPACE}}
labels:
opendatahub.io/dashboard: 'true'
opendatahub.io/managed: 'true'
annotations:
opendatahub.io/connection-type: s3
openshift.io/display-name: ods-ci-ds-pipelines
data:
AWS_ACCESS_KEY_ID: {{AWS_ACCESS_KEY_ID}}
AWS_DEFAULT_REGION: {{AWS_DEFAULT_REGION}}
AWS_S3_BUCKET: {{AWS_S3_BUCKET}}
AWS_S3_ENDPOINT: {{AWS_S3_ENDPOINT}}
AWS_SECRET_ACCESS_KEY: {{AWS_SECRET_ACCESS_KEY}}
type: Opaque
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
apiVersion: datasciencepipelinesapplications.opendatahub.io/v1alpha1
kind: DataSciencePipelinesApplication
metadata:
name: dspa
namespace: {{NAMESPACE}}
spec:
apiServer:
caBundleFileMountPath: ''
stripEOF: true
dbConfigConMaxLifetimeSec: 120
applyTektonCustomResource: true
caBundleFileName: ''
deploy: true
enableSamplePipeline: false
autoUpdatePipelineDefaultVersion: true
archiveLogs: false
terminateStatus: Cancelled
enableOauth: true
trackArtifacts: true
collectMetrics: true
injectDefaultScript: true
database:
disableHealthCheck: false
mariaDB:
deploy: true
pipelineDBName: mlpipeline
pvcSize: 10Gi
username: mlpipeline
dspVersion: v2
objectStorage:
disableHealthCheck: false
enableExternalRoute: false
externalStorage:
basePath: ''
bucket: {{AWS_S3_BUCKET}}
host: s3.amazonaws.com
port: ''
region: us-east-1
s3CredentialsSecret:
accessKey: AWS_ACCESS_KEY_ID
secretKey: AWS_SECRET_ACCESS_KEY
secretName: {{DSPA_SECRET_NAME}}
scheme: https
persistenceAgent:
deploy: true
numWorkers: 2
scheduledWorkflow:
cronScheduleTimezone: UTC
deploy: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
kind: Secret
apiVersion: v1
metadata:
name: {{DSPA_SECRET_NAME}}
namespace: {{NAMESPACE}}
labels:
opendatahub.io/dashboard: 'true'
data:
AWS_ACCESS_KEY_ID: {{AWS_ACCESS_KEY_ID}}
AWS_SECRET_ACCESS_KEY: {{AWS_SECRET_ACCESS_KEY}}
type: Opaque
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { DeleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';
import { Modal } from '~/__tests__/cypress/cypress/pages/components/Modal';
import { appChrome } from '~/__tests__/cypress/cypress/pages/appChrome';

class PipelinesGlobal {
visit(projectName: string) {
cy.visitWithLogin(`/pipelines/${projectName}`);
this.wait();
}

navigate() {
appChrome.findNavItem('Data Science Pipelines').click();
this.wait();
}

private wait() {
cy.findByTestId('app-page-title').contains('Pipelines');
cy.testA11y();
Expand Down Expand Up @@ -42,7 +48,7 @@ class PipelinesGlobal {
return cy.findByRole('menuitem').get('span').contains('Upload new version');
}

private findProjectSelect() {
findProjectSelect() {
return cy.findByTestId('project-selector-dropdown');
}

Expand Down
12 changes: 10 additions & 2 deletions frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ class RunDetails extends PipelinesTopology {
return new DetailsItem(() => cy.findByTestId(`detail-item-${key}`));
}

private findStatusLabel(timeout?: number) {
return cy.findByTestId('status-icon', { timeout });
}

expectStatusLabelToBe(statusValue: string, timeout?: number) {
this.findStatusLabel(timeout).should('have.text', statusValue);
}

findRightDrawer() {
return new PipelineRunRightDrawer(() =>
cy.findByTestId('pipeline-run-drawer-right-content').parent(),
Expand Down Expand Up @@ -169,8 +177,8 @@ class PipelineDetails extends PipelinesTopology {
return new DashboardCodeEditor(() => cy.findByTestId('pipeline-dashboard-code-editor'));
}

findPageTitle() {
return cy.findByTestId('app-page-title');
findPageTitle(timeout?: number) {
return cy.findByTestId('app-page-title', { timeout });
}

getTaskDrawer() {
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ class ProjectListPage {
findCreateWorkbenchButton() {
return cy.findByRole('button', { name: 'Create a workbench' });
}

/**
* Filter Project by name using the Project filter from the Data Science Projects view
* @param projectName Project Name
*/
filterProjectByName = (projectName: string) => {
const projectListToolbar = projectListPage.getTableToolbar();
projectListToolbar.findFilterMenuOption('filter-dropdown-select', 'Name').click();
projectListToolbar.findSearchInput().type(projectName);
};
}

class CreateEditProjectModal extends Modal {
Expand Down Expand Up @@ -159,6 +169,10 @@ class ProjectDetails {
);
}

findImportPipelineButton(timeout?: undefined) {
return cy.findByTestId('import-pipeline-button', { timeout });
}

findSingleModelDeployButton() {
return this.findModelServingPlatform('single').findByTestId('single-serving-deploy-button');
}
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/__tests__/cypress/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ Cypress.Keyboard.defaults({
keystrokeDelay: 0,
});

before(() => {
// disable Cypress's default behavior of logging all XMLHttpRequests and fetches
cy.intercept({ resourceType: /xhr|fetch/ }, { log: false });
});

beforeEach(() => {
if (Cypress.env('MOCK')) {
// fallback: return 404 for all api requests
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { deleteOpenShiftProject } from '~/__tests__/cypress/cypress/utils/oc_commands/project';
import { ADMIN_USER } from '~/__tests__/cypress/cypress/utils/e2eUsers';
import { projectListPage, projectDetails } from '~/__tests__/cypress/cypress/pages/projects';
import { pipelineImportModal } from '~/__tests__/cypress/cypress/pages/pipelines/pipelineImportModal';
import { createRunPage } from '~/__tests__/cypress/cypress/pages/pipelines/createRunPage';
import {
pipelineDetails,
pipelineRunDetails,
} from '~/__tests__/cypress/cypress/pages/pipelines/topology';
import { provisionProjectForPipelines } from '~/__tests__/cypress/cypress/utils/pipelines';

const projectName = 'test-pipelines-prj';
const dspaSecretName = 'dashboard-dspa-secret';
const testPipelineName = 'test-pipelines-pipeline';
const testRunName = 'test-pipelines-run';

describe('An admin user can import and run a pipeline', { testIsolation: false }, () => {
before(() => {
// Create a Project for pipelines
provisionProjectForPipelines(projectName, dspaSecretName);
});

after(() => {
// Delete provisioned Project
deleteOpenShiftProject(projectName);
});

it('An admin User can Import and Run a Pipeline', () => {
// Login as an admin
cy.visitWithLogin('/', ADMIN_USER);

/**
* Import Pipeline by URL from Project Details view
*/
projectListPage.navigate();

// Open the project
projectListPage.filterProjectByName(projectName);
projectListPage.findProjectLink(projectName).click();

// Increasing the timeout to ~3mins so the DSPA can be loaded
projectDetails.findImportPipelineButton(180000).click();

// Fill tue Import Pipeline modal
pipelineImportModal.findPipelineNameInput().type(testPipelineName);
pipelineImportModal.findPipelineDescriptionInput().type('Pipeline Description');
pipelineImportModal.findImportPipelineRadio().click();
pipelineImportModal
.findPipelineUrlInput()
//TODO: modify this URL once the PR is merged
.type(
'https://raw.githubusercontent.com/opendatahub-io/odh-dashboard/caab82536b4dd5d39fb7a06a6c3248f10c183417/frontend/src/__tests__/resources/pipelines_samples/dummy_pipeline_compiled.yaml',
);
pipelineImportModal.submit();

// Verify that we are at the details page of the pipeline by checking the title
// It can take a little longer to load
pipelineDetails.findPageTitle(60000).should('have.text', testPipelineName);

/**
* Run the Pipeline using the Actions button in the pipeline detail view
*/

pipelineDetails.selectActionDropdownItem('Create run');

//Fill the Create run fields
createRunPage.findExperimentSelect().click();
createRunPage.selectExperimentByName('Default');
createRunPage.fillName(testRunName);
createRunPage.fillDescription('Run Description');
createRunPage.findSubmitButton().click();

//Redirected to the Graph view of the created run
pipelineRunDetails.expectStatusLabelToBe('Succeeded', 180000);
});
});
41 changes: 41 additions & 0 deletions frontend/src/__tests__/cypress/cypress/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,49 @@ export type UserAuthConfig = {
PASSWORD: string;
};

export type AWSS3BucketDetails = {
NAME: string;
REGION: string;
ENDPOINT: string;
};

export type AWSS3Buckets = {
AWS_ACCESS_KEY_ID: string;
AWS_SECRET_ACCESS_KEY: string;
BUCKET_2: AWSS3BucketDetails;
};

export type DataConnectionReplacements = {
NAMESPACE: string;
AWS_ACCESS_KEY_ID: string;
AWS_DEFAULT_REGION: string;
AWS_S3_BUCKET: string;
AWS_S3_ENDPOINT: string;
AWS_SECRET_ACCESS_KEY: string;
};

export type DspaSecretReplacements = {
DSPA_SECRET_NAME: string;
NAMESPACE: string;
AWS_ACCESS_KEY_ID: string;
AWS_SECRET_ACCESS_KEY: string;
};

export type DspaReplacements = {
DSPA_SECRET_NAME: string;
NAMESPACE: string;
AWS_S3_BUCKET: string;
};

export type CommandLineResult = {
code: number;
stdout: string;
stderr: string;
};

export type TestConfig = {
ODH_DASHBOARD_URL: string;
TEST_USER: UserAuthConfig;
OCP_ADMIN_USER: UserAuthConfig;
S3: AWSS3Buckets;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { CommandLineResult } from '~/__tests__/cypress/cypress/types';

/**
* Applies the given YAML content using the `oc apply` command.
*
* @param yamlContent YAML content to be applied
* @returns Cypress Chainable
*/
export const applyOpenShiftYaml = (yamlContent: string): Cypress.Chainable<CommandLineResult> => {
const ocCommand = `oc apply -f - <<EOF\n${yamlContent}\nEOF`;
return cy.exec(ocCommand, { failOnNonZeroExit: false }).then((result: CommandLineResult) => {
if (result.code !== 0) {
// If there is an error, log the error and fail the test
cy.log(`ERROR applying YAML content
stdout: ${result.stdout}
stderr: ${result.stderr}`);
throw new Error(`Command failed with code ${result.code}`);
}
return result;
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type {
DataConnectionReplacements,
CommandLineResult,
} from '~/__tests__/cypress/cypress/types';
import { replacePlaceholdersInYaml } from '~/__tests__/cypress/cypress/utils/yaml_files';
import { applyOpenShiftYaml } from './baseCommands';

/**
* Try to create a data connection based on the dataConnectionReplacements config
* @param dataConnectionReplacements Dictionary with the config values
* Dict Structure:
* dataConnectionReplacements = {
* NAMESPACE: <PROJECT NAME>,
* AWS_ACCESS_KEY_ID: <AWS ACCESS KEY ID>,
* AWS_DEFAULT_REGION: <AWS REGION>,
* AWS_S3_BUCKET: <AWS BUCKET NAME>,
* AWS_S3_ENDPOINT: <AWS ENDPOINT>,
* AWS_SECRET_ACCESS_KEY: <AWS SECRET>,
* }
* @param yamlFilePath
*/
export const createDataConnection = (
dataConnectionReplacements: DataConnectionReplacements,
yamlFilePath = 'resources/yaml/data_connection.yaml',
): Cypress.Chainable<CommandLineResult> => {
return cy.fixture(yamlFilePath).then((yamlContent) => {
const modifiedYamlContent = replacePlaceholdersInYaml(yamlContent, dataConnectionReplacements);
return applyOpenShiftYaml(modifiedYamlContent);
});
};
Loading

0 comments on commit d786249

Please sign in to comment.