From e5415d2e5b66a3bed795b033e4a3ac85cba97d4b Mon Sep 17 00:00:00 2001 From: pnaik1 Date: Thu, 25 Apr 2024 14:58:58 +0530 Subject: [PATCH] additional cypress test case for pipeline detail --- .../e2e/pipelines/PipelinesTopology.cy.ts | 122 ++++++++++++++++-- .../cypress/pages/pipelines/topology.ts | 75 +++++++++-- .../pipelinesDetails/PipelineDetailsYAML.tsx | 1 + .../pipeline/PipelineDetails.tsx | 1 + .../pipeline/PipelineTaskDetails.tsx | 16 ++- .../pipeline/SelectedTaskDrawerContent.tsx | 2 +- .../taskDetails/TaskDetailsCodeBlock.tsx | 4 +- .../taskDetails/TaskDetailsInputOutput.tsx | 4 +- .../taskDetails/TaskDetailsSection.tsx | 5 +- 9 files changed, 202 insertions(+), 28 deletions(-) diff --git a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelinesTopology.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelinesTopology.cy.ts index ad46521358..f5abc98a97 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelinesTopology.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/pipelines/PipelinesTopology.cy.ts @@ -2,7 +2,10 @@ import { mockDataSciencePipelineApplicationK8sResource } from '~/__mocks__/mockDataSciencePipelinesApplicationK8sResource'; import { mockDscStatus } from '~/__mocks__/mockDscStatus'; import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList'; -import { buildMockPipelineVersionV2 } from '~/__mocks__/mockPipelineVersionsProxy'; +import { + buildMockPipelineVersionV2, + buildMockPipelineVersionsV2, +} from '~/__mocks__/mockPipelineVersionsProxy'; import { mockProjectK8sResource } from '~/__mocks__/mockProjectK8sResource'; import { mockRouteK8sResource } from '~/__mocks__/mockRouteK8sResource'; import { mockSecretK8sResource } from '~/__mocks__/mockSecretK8sResource'; @@ -12,6 +15,7 @@ import { pipelineDetails, pipelineRunJobDetails, pipelineRunDetails, + pipelineVersionImportModal, } from '~/__tests__/cypress/cypress/pages/pipelines'; import { buildMockRunKF } from '~/__mocks__/mockRunKF'; import { mockPipelinePodK8sResource } from '~/__mocks__/mockPipelinePodK8sResource'; @@ -24,6 +28,8 @@ import { RouteModel, SecretModel, } from '~/__tests__/cypress/cypress/utils/models'; +import { mock200Status } from '~/__mocks__/mockK8sStatus'; +import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal'; const projectId = 'test-project'; const mockPipeline = buildMockPipelineV2({ @@ -35,6 +41,11 @@ const mockVersion = buildMockPipelineVersionV2({ pipeline_version_id: 'test-version-id', display_name: 'test-version-name', }); +const mockVersion2 = buildMockPipelineVersionV2({ + pipeline_id: mockPipeline.pipeline_id, + pipeline_version_id: 'test-version-id-2', + display_name: 'test-version-2', +}); const mockRun = buildMockRunKF({ display_name: 'test-pipeline-run', run_id: 'test-pipeline-run-id', @@ -109,6 +120,13 @@ const initIntercepts = () => { }, mockPipeline, ); + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectId}/dspa/apis/v2beta1/pipelines/${mockPipeline.pipeline_id}/versions`, + }, + buildMockPipelineVersionsV2([mockVersion, mockVersion2]), + ); cy.intercept( { @@ -181,16 +199,15 @@ const initIntercepts = () => { describe('Pipeline topology', () => { describe('Pipeline details', () => { + beforeEach(() => { + initIntercepts(); + pipelineDetails.visit(projectId, mockVersion.pipeline_id, mockVersion.pipeline_version_id); + // https://issues.redhat.com/browse/RHOAIENG-4562 + // Bypass intermittent Cypress error: + // Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs/base/worker/workerMain.js' failed to load. + Cypress.on('uncaught:exception', () => false); + }); describe('Navigation', () => { - beforeEach(() => { - initIntercepts(); - pipelineDetails.visit(projectId, mockVersion.pipeline_id, mockVersion.pipeline_version_id); - // https://issues.redhat.com/browse/RHOAIENG-4562 - // Bypass intermittent Cypress error: - // Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs/base/worker/workerMain.js' failed to load. - Cypress.on('uncaught:exception', () => false); - }); - it('Test pipeline details create run navigation', () => { pipelineDetails.selectActionDropdownItem('Create run'); verifyRelativeURL(`/pipelineRuns/${projectId}/pipelineRun/create`); @@ -211,6 +228,91 @@ describe('Pipeline topology', () => { verifyRelativeURL(`/pipelineRuns/${projectId}?runType=scheduled`); }); }); + + it('validate clicking on node will open a drawer from right with data.', () => { + pipelineDetails.findTaskNode('create-dataset').click(); + const taskDrawer = pipelineDetails.getTaskDrawer(); + taskDrawer.shouldHaveTaskName('create-dataset '); + taskDrawer.findInputArtifacts().should('not.exist'); + taskDrawer.findOutputParameters().should('not.exist'); + taskDrawer.findOutputArtifacts().should('exist'); + taskDrawer.findCommandCodeBlock().should('not.be.empty'); + taskDrawer.findArgumentCodeBlock().should('not.be.empty'); + taskDrawer.findTaskImage().should('have.text', 'Imagequay.io/hukhan/iris-base:1'); + taskDrawer.findCloseDrawerButton().click(); + taskDrawer.find().should('not.exist'); + }); + + it('delete pipeline version from action dropdown', () => { + pipelineDetails.selectActionDropdownItem('Delete pipeline version'); + deleteModal.shouldBeOpen(); + deleteModal.findInput().type(mockVersion.display_name); + cy.intercept( + { + method: 'DELETE', + pathname: `/api/service/pipelines/${projectId}/dspa/apis/v2beta1/pipelines/${mockPipeline.pipeline_id}/versions/${mockVersion.pipeline_version_id}`, + }, + mock200Status({}), + ).as('deletePipelineVersion'); + + deleteModal.findSubmitButton().click(); + cy.wait('@deletePipelineVersion'); + }); + + it('page details are updated when a new pipeline version is selected', () => { + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectId}/dspa/apis/v2beta1/pipelines/${mockPipeline.pipeline_id}/versions/${mockVersion2.pipeline_version_id}`, + }, + mockVersion2, + ); + pipelineDetails.selectPipelineVersionByName(mockVersion2.display_name); + pipelineDetails.findPageTitle().should('have.text', 'test-version-2'); + verifyRelativeURL( + `/pipelines/${projectId}/pipeline/view/${mockPipeline.pipeline_id}/${mockVersion2.pipeline_version_id}`, + ); + }); + + it('page details are updated after uploading a new version', () => { + cy.intercept( + { + method: 'GET', + pathname: `/api/service/pipelines/${projectId}/dspa/apis/v2beta1/pipelines/${mockPipeline.pipeline_id}/versions/${mockVersion2.pipeline_version_id}`, + }, + mockVersion2, + ); + pipelineDetails.findPageTitle().should('have.text', 'test-version-name'); + pipelineDetails.selectActionDropdownItem('Upload new version'); + pipelineVersionImportModal.findImportPipelineRadio().check(); + pipelineVersionImportModal.findPipelineUrlInput().type('https://example.com/pipeline.yaml'); + cy.intercept( + { + method: 'POST', + pathname: `/api/service/pipelines/${projectId}/dspa/apis/v2beta1/pipelines/${mockPipeline.pipeline_id}/versions`, + }, + mockVersion2, + ).as('uploadNewPipelineVersion'); + + pipelineVersionImportModal.submit(); + verifyRelativeURL( + `/pipelines/${projectId}/pipeline/view/${mockPipeline.pipeline_id}/${mockVersion2.pipeline_version_id}`, + ); + cy.wait('@uploadNewPipelineVersion').then((interception) => { + expect(interception.request.body).to.containSubset({ + pipeline_id: 'test-pipeline', + description: '', + package_url: { pipeline_url: 'https://example.com/pipeline.yaml' }, + }); + }); + pipelineDetails.findPageTitle().should('have.text', 'test-version-2'); + }); + + it('validate for Yaml tab in pipeline details tab', () => { + pipelineDetails.findYamlTab().click(); + const pipelineDashboardCodeEditor = pipelineDetails.getPipelineDashboardCodeEditor(); + pipelineDashboardCodeEditor.findInput().should('not.be.empty'); + }); }); describe('Pipeline run details', () => { diff --git a/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts b/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts index 6a26612456..0f46d02ade 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/pipelines/topology.ts @@ -1,4 +1,39 @@ import { Contextual } from '~/__tests__/cypress/cypress/pages/components/Contextual'; +import { DashboardCodeEditor } from '~/__tests__/cypress/cypress/pages/components/DashboardCodeEditor'; + +class TaskDrawer extends Contextual { + findInputArtifacts() { + return this.find().findByTestId('Input-artifacts'); + } + + findCommandCodeBlock() { + return this.find().findByTestId('command-task-detail-code-block').findByRole('code'); + } + + findTaskImage() { + return this.find().findByTestId('task-detail-image'); + } + + findArgumentCodeBlock() { + return this.find().findByTestId('arguments-task-detail-code-block').findByRole('code'); + } + + findOutputArtifacts() { + return this.find().findByTestId('Output-artifacts'); + } + + findOutputParameters() { + return this.find().findByTestId('Output-parameters'); + } + + findCloseDrawerButton() { + return this.find().findByRole('button', { name: 'Close drawer panel' }); + } + + shouldHaveTaskName(name: string) { + return this.find().findByTestId('pipeline-task-name').should('have.text', name); + } +} class PipelinesTopology { visit(namespace: string, pipelineId: string, pipelineVersionId: string) { @@ -13,14 +48,6 @@ class PipelinesTopology { findTaskNode(name: string) { return cy.get(`[data-id="${name}"][data-kind="node"][data-type="DEFAULT_TASK_NODE"]`); } - - findTaskDrawer() { - return cy.findByTestId('task-drawer'); - } - - findCloseDrawerButton() { - return this.findTaskDrawer().findByRole('button', { name: 'Close drawer panel' }); - } } class PipelineRunRightDrawer extends Contextual { @@ -75,7 +102,37 @@ class PipelineDetails extends PipelinesTopology { this.wait(); } - findActionsDropdown() { + private findPipelineVersionSelect() { + return cy.findByTestId('pipeline-version-toggle-button'); + } + + selectPipelineVersionByName(name: string): void { + this.findPipelineVersionSelect() + .click() + .parents() + .findByTestId('pipeline-version-selector-table-list') + .find('td') + .contains(name) + .click(); + } + + findYamlTab() { + return cy.findByTestId('pipeline-yaml-tab'); + } + + getPipelineDashboardCodeEditor() { + return new DashboardCodeEditor(() => cy.findByTestId('pipeline-dashboard-code-editor')); + } + + findPageTitle() { + return cy.findByTestId('app-page-title'); + } + + getTaskDrawer() { + return new TaskDrawer(() => cy.findByTestId('task-drawer')); + } + + private findActionsDropdown() { return cy.findByTestId('pipeline-version-details-actions'); } diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/PipelineDetailsYAML.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/PipelineDetailsYAML.tsx index 7fd8b87a3d..88001f2edc 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/PipelineDetailsYAML.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/PipelineDetailsYAML.tsx @@ -33,6 +33,7 @@ const PipelineDetailsYAML: React.FC = ({ filename, con return ( YAML} + data-testid="pipeline-yaml-tab" aria-label="Pipeline YAML Tab" tabContentId={`tabContent-${PipelineDetailsTab.YAML}`} /> diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineTaskDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineTaskDetails.tsx index d115c44e99..f664c4949d 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineTaskDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineTaskDetails.tsx @@ -49,19 +49,29 @@ const PipelineTaskDetails: React.FC = ({ task }) => { {task.steps?.map((step, i) => ( - {step.image} + + {step.image} + {step.command && ( - + )} {step.args && ( - + )} diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/SelectedTaskDrawerContent.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/SelectedTaskDrawerContent.tsx index 3f630498ea..7ab4744044 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/SelectedTaskDrawerContent.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/SelectedTaskDrawerContent.tsx @@ -28,7 +28,7 @@ const SelectedTaskDrawerContent: React.FC = ({ t data-testid="task-drawer" > - + <Title headingLevel="h2" size="xl" data-testid="pipeline-task-name"> {task.name} {task.type === 'artifact' ? 'Artifact details' : ''} diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/taskDetails/TaskDetailsCodeBlock.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/taskDetails/TaskDetailsCodeBlock.tsx index 18c5adbe10..263938da4d 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/taskDetails/TaskDetailsCodeBlock.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/taskDetails/TaskDetailsCodeBlock.tsx @@ -9,9 +9,10 @@ import { type TaskDetailsCodeBlockProps = { id: string; content: string; + testId?: string; }; -const TaskDetailsCodeBlock: React.FC = ({ id, content }) => { +const TaskDetailsCodeBlock: React.FC = ({ id, content, testId }) => { const [copied, setCopied] = React.useState(false); const clipboardCopyFunc = (text: string) => { @@ -25,6 +26,7 @@ const TaskDetailsCodeBlock: React.FC = ({ id, content return ( = ({ {artifacts && ( - + )} {params && ( - + diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/taskDetails/TaskDetailsSection.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/taskDetails/TaskDetailsSection.tsx index cba64e9e59..aa97168d7b 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/taskDetails/TaskDetailsSection.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/taskDetails/TaskDetailsSection.tsx @@ -4,10 +4,11 @@ import { Stack, StackItem, Title } from '@patternfly/react-core'; type TaskDetailsSectionProps = { title: string; children: React.ReactNode; + testId?: string; }; -const TaskDetailsSection: React.FC = ({ title, children }) => ( - +const TaskDetailsSection: React.FC = ({ title, children, testId }) => ( + {title}