mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 10:39:30 +00:00 
			
		
		
		
	playwright: migrate incident manager test to playwright (#17657)
* playwright: migrate incident manager test to playwright * migrate remaining test to playwright * added serial mode to prevent load while deploying pipeline
This commit is contained in:
		
							parent
							
								
									53cc84aef1
								
							
						
					
					
						commit
						b9311526a7
					
				| @ -1,538 +0,0 @@ | |||||||
| /* |  | ||||||
|  *  Copyright 2024 Collate. |  | ||||||
|  *  Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  *  you may not use this file except in compliance with the License. |  | ||||||
|  *  You may obtain a copy of the License at |  | ||||||
|  *  http://www.apache.org/licenses/LICENSE-2.0
 |  | ||||||
|  *  Unless required by applicable law or agreed to in writing, software |  | ||||||
|  *  distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  *  See the License for the specific language governing permissions and |  | ||||||
|  *  limitations under the License. |  | ||||||
|  */ |  | ||||||
| import { interceptURL, verifyResponseStatusCode } from '../../common/common'; |  | ||||||
| import { triggerTestCasePipeline } from '../../common/Utils/DataQuality'; |  | ||||||
| import { |  | ||||||
|   createEntityTableViaREST, |  | ||||||
|   deleteEntityViaREST, |  | ||||||
|   visitEntityDetailsPage, |  | ||||||
| } from '../../common/Utils/Entity'; |  | ||||||
| import { getToken } from '../../common/Utils/LocalStorage'; |  | ||||||
| import { generateRandomUser } from '../../common/Utils/Owner'; |  | ||||||
| import { uuid } from '../../constants/constants'; |  | ||||||
| import { EntityType, SidebarItem } from '../../constants/Entity.interface'; |  | ||||||
| import { DATABASE_SERVICE } from '../../constants/EntityConstant'; |  | ||||||
| const TABLE_NAME = DATABASE_SERVICE.entity.name; |  | ||||||
| 
 |  | ||||||
| const testSuite = { |  | ||||||
|   name: `${DATABASE_SERVICE.entity.databaseSchema}.${DATABASE_SERVICE.entity.name}.testSuite`, |  | ||||||
|   executableEntityReference: `${DATABASE_SERVICE.entity.databaseSchema}.${DATABASE_SERVICE.entity.name}`, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const testCases = [ |  | ||||||
|   `cy_first_table_column_count_to_be_between_${uuid()}`, |  | ||||||
|   `cy_second_table_column_count_to_be_between_${uuid()}`, |  | ||||||
|   `cy_third_table_column_count_to_be_between_${uuid()}`, |  | ||||||
| ]; |  | ||||||
| const user1 = generateRandomUser(); |  | ||||||
| const user2 = generateRandomUser(); |  | ||||||
| const user3 = generateRandomUser(); |  | ||||||
| const userData1 = { |  | ||||||
|   displayName: `${user1.firstName}${user1.lastName}`, |  | ||||||
|   name: user1.email.split('@')[0], |  | ||||||
| }; |  | ||||||
| const userData2 = { |  | ||||||
|   displayName: `${user2.firstName}${user2.lastName}`, |  | ||||||
|   name: user2.email.split('@')[0], |  | ||||||
| }; |  | ||||||
| const userData3 = { |  | ||||||
|   displayName: `${user3.firstName}${user3.lastName}`, |  | ||||||
|   name: user3.email.split('@')[0], |  | ||||||
| }; |  | ||||||
| const userIds: string[] = []; |  | ||||||
| 
 |  | ||||||
| const goToProfilerTab = () => { |  | ||||||
|   interceptURL( |  | ||||||
|     'GET', |  | ||||||
|     `api/v1/tables/name/${DATABASE_SERVICE.service.name}.*.${TABLE_NAME}?fields=*&include=all`, |  | ||||||
|     'waitForPageLoad' |  | ||||||
|   ); |  | ||||||
|   visitEntityDetailsPage({ |  | ||||||
|     term: TABLE_NAME, |  | ||||||
|     serviceName: DATABASE_SERVICE.service.name, |  | ||||||
|     entity: EntityType.Table, |  | ||||||
|   }); |  | ||||||
|   verifyResponseStatusCode('@waitForPageLoad', 200); |  | ||||||
| 
 |  | ||||||
|   cy.get('[data-testid="profiler"]').should('be.visible').click(); |  | ||||||
|   cy.get('[data-testid="profiler-tab-left-panel"]') |  | ||||||
|     .contains('Table Profile') |  | ||||||
|     .click(); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const acknowledgeTask = (testCase: string) => { |  | ||||||
|   goToProfilerTab(); |  | ||||||
| 
 |  | ||||||
|   cy.get('[data-testid="profiler-tab-left-panel"]') |  | ||||||
|     .contains('Data Quality') |  | ||||||
|     .click(); |  | ||||||
|   cy.get(`[data-testid="${testCase}"]`) |  | ||||||
|     .find('.last-run-box.failed') |  | ||||||
|     .scrollIntoView() |  | ||||||
|     .should('be.visible'); |  | ||||||
|   cy.get(`[data-testid="${testCase}-status"]`).should('contain', 'New'); |  | ||||||
|   cy.get(`[data-testid="${testCase}"]`).contains(testCase).click(); |  | ||||||
|   cy.get('[data-testid="edit-resolution-icon"]').click(); |  | ||||||
|   cy.get('[data-testid="test-case-resolution-status-type"]').click(); |  | ||||||
|   cy.get('[title="Ack"]').click(); |  | ||||||
|   interceptURL( |  | ||||||
|     'POST', |  | ||||||
|     '/api/v1/dataQuality/testCases/testCaseIncidentStatus', |  | ||||||
|     'updateTestCaseIncidentStatus' |  | ||||||
|   ); |  | ||||||
|   cy.get('#update-status-button').click(); |  | ||||||
|   verifyResponseStatusCode('@updateTestCaseIncidentStatus', 200); |  | ||||||
|   cy.get(`[data-testid="${testCase}-status"]`).should('contain', 'Ack'); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const assignIncident = (testCaseName: string) => { |  | ||||||
|   cy.sidebarClick(SidebarItem.INCIDENT_MANAGER); |  | ||||||
|   cy.get(`[data-testid="test-case-${testCaseName}"]`).should('be.visible'); |  | ||||||
|   cy.get(`[data-testid="${testCaseName}-status"]`) |  | ||||||
|     .find(`[data-testid="edit-resolution-icon"]`) |  | ||||||
|     .click(); |  | ||||||
|   cy.get(`[data-testid="test-case-resolution-status-type"]`).click(); |  | ||||||
|   cy.get(`[title="Assigned"]`).click(); |  | ||||||
|   cy.get('#testCaseResolutionStatusDetails_assignee').should('be.visible'); |  | ||||||
|   interceptURL( |  | ||||||
|     'GET', |  | ||||||
|     `/api/v1/search/suggest?q=*${user1.firstName}*${user1.lastName}*&index=user_search_index*`, |  | ||||||
|     'searchAssignee' |  | ||||||
|   ); |  | ||||||
|   interceptURL('GET', '/api/v1/users/name/*', 'userList'); |  | ||||||
|   cy.get('#testCaseResolutionStatusDetails_assignee').click(); |  | ||||||
|   cy.wait('@userList'); |  | ||||||
|   cy.get('#testCaseResolutionStatusDetails_assignee').type( |  | ||||||
|     userData1.displayName |  | ||||||
|   ); |  | ||||||
|   verifyResponseStatusCode('@searchAssignee', 200); |  | ||||||
|   cy.get(`[data-testid="${userData1.name.toLocaleLowerCase()}"]`).click(); |  | ||||||
|   interceptURL( |  | ||||||
|     'POST', |  | ||||||
|     '/api/v1/dataQuality/testCases/testCaseIncidentStatus', |  | ||||||
|     'updateTestCaseIncidentStatus' |  | ||||||
|   ); |  | ||||||
|   cy.get('#update-status-button').click(); |  | ||||||
|   verifyResponseStatusCode('@updateTestCaseIncidentStatus', 200); |  | ||||||
|   cy.get( |  | ||||||
|     `[data-testid="${testCaseName}-status"] [data-testid="badge-container"]` |  | ||||||
|   ).should('contain', 'Assigned'); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| describe('Incident Manager', { tags: 'Observability' }, () => { |  | ||||||
|   before(() => { |  | ||||||
|     cy.login(); |  | ||||||
| 
 |  | ||||||
|     cy.getAllLocalStorage().then((data) => { |  | ||||||
|       const token = getToken(data); |  | ||||||
| 
 |  | ||||||
|       // Create a new user
 |  | ||||||
|       for (const user of [user1, user2, user3]) { |  | ||||||
|         cy.request({ |  | ||||||
|           method: 'POST', |  | ||||||
|           url: `/api/v1/users/signup`, |  | ||||||
|           headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|           body: user, |  | ||||||
|         }).then((response) => { |  | ||||||
|           userIds.push(response.body.id); |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       createEntityTableViaREST({ |  | ||||||
|         token, |  | ||||||
|         ...DATABASE_SERVICE, |  | ||||||
|         tables: [DATABASE_SERVICE.entity], |  | ||||||
|       }); |  | ||||||
|       // create testSuite
 |  | ||||||
|       cy.request({ |  | ||||||
|         method: 'POST', |  | ||||||
|         url: `/api/v1/dataQuality/testSuites/executable`, |  | ||||||
|         headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|         body: testSuite, |  | ||||||
|       }).then((testSuiteResponse) => { |  | ||||||
|         // creating test case
 |  | ||||||
| 
 |  | ||||||
|         testCases.forEach((testCase) => { |  | ||||||
|           cy.request({ |  | ||||||
|             method: 'POST', |  | ||||||
|             url: `/api/v1/dataQuality/testCases`, |  | ||||||
|             headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|             body: { |  | ||||||
|               name: testCase, |  | ||||||
|               entityLink: `<#E::table::${testSuite.executableEntityReference}>`, |  | ||||||
|               parameterValues: [ |  | ||||||
|                 { name: 'minColValue', value: 12 }, |  | ||||||
|                 { name: 'maxColValue', value: 24 }, |  | ||||||
|               ], |  | ||||||
|               testDefinition: 'tableColumnCountToBeBetween', |  | ||||||
|               testSuite: testSuite.name, |  | ||||||
|             }, |  | ||||||
|           }); |  | ||||||
|         }); |  | ||||||
|         cy.request({ |  | ||||||
|           method: 'POST', |  | ||||||
|           url: `/api/v1/services/ingestionPipelines`, |  | ||||||
|           headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|           body: { |  | ||||||
|             airflowConfig: {}, |  | ||||||
|             name: `${testSuite.executableEntityReference}_test_suite`, |  | ||||||
|             pipelineType: 'TestSuite', |  | ||||||
|             service: { |  | ||||||
|               id: testSuiteResponse.body.id, |  | ||||||
|               type: 'testSuite', |  | ||||||
|             }, |  | ||||||
|             sourceConfig: { |  | ||||||
|               config: { |  | ||||||
|                 type: 'TestSuite', |  | ||||||
|                 entityFullyQualifiedName: testSuite.executableEntityReference, |  | ||||||
|               }, |  | ||||||
|             }, |  | ||||||
|           }, |  | ||||||
|         }).then((response) => |  | ||||||
|           cy.request({ |  | ||||||
|             method: 'POST', |  | ||||||
|             url: `/api/v1/services/ingestionPipelines/deploy/${response.body.id}`, |  | ||||||
|             headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|           }) |  | ||||||
|         ); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     triggerTestCasePipeline({ |  | ||||||
|       serviceName: DATABASE_SERVICE.service.name, |  | ||||||
|       tableName: TABLE_NAME, |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   after(() => { |  | ||||||
|     cy.login(); |  | ||||||
| 
 |  | ||||||
|     cy.getAllLocalStorage().then((data) => { |  | ||||||
|       const token = getToken(data); |  | ||||||
|       deleteEntityViaREST({ |  | ||||||
|         token, |  | ||||||
|         endPoint: EntityType.DatabaseService, |  | ||||||
|         entityName: DATABASE_SERVICE.service.name, |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       // Delete created user
 |  | ||||||
|       userIds.forEach((userId) => { |  | ||||||
|         cy.request({ |  | ||||||
|           method: 'DELETE', |  | ||||||
|           url: `/api/v1/users/${userId}?hardDelete=true&recursive=false`, |  | ||||||
|           headers: { Authorization: `Bearer ${token}` }, |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('Basic Scenario', () => { |  | ||||||
|     const testCaseName = testCases[0]; |  | ||||||
| 
 |  | ||||||
|     beforeEach(() => { |  | ||||||
|       cy.login(); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it("Acknowledge table test case's failure", () => { |  | ||||||
|       acknowledgeTask(testCaseName); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Assign incident to user', () => { |  | ||||||
|       assignIncident(testCaseName); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Re-assign incident to user', () => { |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases/name/*?fields=*', |  | ||||||
|         'getTestCase' |  | ||||||
|       ); |  | ||||||
|       interceptURL('GET', '/api/v1/feed?entityLink=*&type=Task', 'getTaskFeed'); |  | ||||||
|       cy.sidebarClick(SidebarItem.INCIDENT_MANAGER); |  | ||||||
|       cy.get(`[data-testid="test-case-${testCaseName}"]`).click(); |  | ||||||
|       verifyResponseStatusCode('@getTestCase', 200); |  | ||||||
|       cy.get('[data-testid="incident"]').click(); |  | ||||||
|       verifyResponseStatusCode('@getTaskFeed', 200); |  | ||||||
|       cy.get('[data-testid="task-cta-buttons"] [role="img"]') |  | ||||||
|         .scrollIntoView() |  | ||||||
|         .click(); |  | ||||||
|       cy.get('[role="menu"').find('[data-menu-id*="re-assign"]').click(); |  | ||||||
| 
 |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         `/api/v1/search/suggest?q=*${user2.firstName}*${user2.lastName}*&index=user_search_index*`, |  | ||||||
|         'searchAssignee' |  | ||||||
|       ); |  | ||||||
|       interceptURL('GET', '/api/v1/users/name/*', 'userList'); |  | ||||||
|       cy.get('[data-testid="select-assignee"]').click(); |  | ||||||
|       cy.wait('@userList'); |  | ||||||
|       cy.get('[data-testid="select-assignee"]').type(userData2.displayName); |  | ||||||
|       verifyResponseStatusCode('@searchAssignee', 200); |  | ||||||
|       cy.get(`[data-testid="${userData2.name.toLocaleLowerCase()}"]`).click(); |  | ||||||
| 
 |  | ||||||
|       interceptURL( |  | ||||||
|         'POST', |  | ||||||
|         '/api/v1/dataQuality/testCases/testCaseIncidentStatus', |  | ||||||
|         'updateTestCaseIncidentStatus' |  | ||||||
|       ); |  | ||||||
|       cy.get('.ant-modal-footer').contains('Submit').click(); |  | ||||||
|       verifyResponseStatusCode('@updateTestCaseIncidentStatus', 200); |  | ||||||
|       // Todo: skipping this for now as its not working from backend
 |  | ||||||
|       cy.clickOnLogo(); |  | ||||||
|       cy.get('[id*="tab-tasks"]').click(); |  | ||||||
|       cy.get('[data-testid="task-feed-card"]') |  | ||||||
|         .contains(testCaseName) |  | ||||||
|         .scrollIntoView() |  | ||||||
|         .should('be.visible'); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it("Re-assign incident from test case page's header", () => { |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases/name/*?fields=*', |  | ||||||
|         'getTestCase' |  | ||||||
|       ); |  | ||||||
|       interceptURL('GET', '/api/v1/feed?entityLink=*&type=Task', 'getTaskFeed'); |  | ||||||
|       cy.sidebarClick(SidebarItem.INCIDENT_MANAGER); |  | ||||||
|       cy.get(`[data-testid="test-case-${testCaseName}"]`).click(); |  | ||||||
|       verifyResponseStatusCode('@getTestCase', 200); |  | ||||||
|       interceptURL('GET', '/api/v1/users?*', 'getUsers'); |  | ||||||
|       cy.get('[data-testid="assignee"] [data-testid="edit-owner"]').click(); |  | ||||||
|       verifyResponseStatusCode('@getUsers', 200); |  | ||||||
|       cy.get('[data-testid="loader"]').should('not.exist'); |  | ||||||
|       interceptURL('GET', `api/v1/search/query?q=*`, 'searchOwner'); |  | ||||||
|       cy.get('[data-testid="owner-select-users-search-bar"]').type( |  | ||||||
|         userData3.displayName |  | ||||||
|       ); |  | ||||||
|       verifyResponseStatusCode('@searchOwner', 200); |  | ||||||
|       interceptURL( |  | ||||||
|         'POST', |  | ||||||
|         '/api/v1/dataQuality/testCases/testCaseIncidentStatus', |  | ||||||
|         'updateTestCaseIncidentStatus' |  | ||||||
|       ); |  | ||||||
|       cy.get(`.ant-popover [title="${userData3.displayName}"]`).click(); |  | ||||||
|       verifyResponseStatusCode('@updateTestCaseIncidentStatus', 200); |  | ||||||
|       cy.get('[data-testid="assignee"] [data-testid="owner-link"]').should( |  | ||||||
|         'contain', |  | ||||||
|         userData3.displayName |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Resolve incident', () => { |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases/name/*?fields=*', |  | ||||||
|         'getTestCase' |  | ||||||
|       ); |  | ||||||
|       interceptURL('GET', '/api/v1/feed?entityLink=*&type=Task', 'getTaskFeed'); |  | ||||||
|       cy.sidebarClick(SidebarItem.INCIDENT_MANAGER); |  | ||||||
|       cy.get(`[data-testid="test-case-${testCaseName}"]`).click(); |  | ||||||
|       verifyResponseStatusCode('@getTestCase', 200); |  | ||||||
|       cy.get('[data-testid="incident"]').click(); |  | ||||||
|       verifyResponseStatusCode('@getTaskFeed', 200); |  | ||||||
|       cy.get('[data-testid="task-cta-buttons"]') |  | ||||||
|         .contains('Resolve') |  | ||||||
|         .scrollIntoView() |  | ||||||
|         .click(); |  | ||||||
|       cy.get('#testCaseFailureReason').click(); |  | ||||||
|       cy.get('[title="Missing Data"]').click(); |  | ||||||
|       cy.get('.toastui-editor-md-container > .toastui-editor > .ProseMirror') |  | ||||||
|         .click() |  | ||||||
|         .type('test'); |  | ||||||
|       interceptURL( |  | ||||||
|         'POST', |  | ||||||
|         '/api/v1/dataQuality/testCases/testCaseIncidentStatus', |  | ||||||
|         'updateTestCaseIncidentStatus' |  | ||||||
|       ); |  | ||||||
|       cy.get('.ant-modal-footer').contains('Submit').click(); |  | ||||||
|       verifyResponseStatusCode('@updateTestCaseIncidentStatus', 200); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('Resolving incident & re-run pipeline', () => { |  | ||||||
|     const testName = testCases[1]; |  | ||||||
| 
 |  | ||||||
|     beforeEach(() => { |  | ||||||
|       cy.login(); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it("Acknowledge table test case's failure", () => { |  | ||||||
|       acknowledgeTask(testName); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Resolve task from incident list page', () => { |  | ||||||
|       goToProfilerTab(); |  | ||||||
| 
 |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases?fields=*&entityLink=*&includeAllTests=true&limit=*', |  | ||||||
|         'testCaseList' |  | ||||||
|       ); |  | ||||||
|       cy.get('[data-testid="profiler-tab-left-panel"]') |  | ||||||
|         .contains('Data Quality') |  | ||||||
|         .click(); |  | ||||||
|       verifyResponseStatusCode('@testCaseList', 200); |  | ||||||
|       cy.get(`[data-testid="${testName}"]`) |  | ||||||
|         .find('.last-run-box.failed') |  | ||||||
|         .scrollIntoView() |  | ||||||
|         .should('be.visible'); |  | ||||||
|       cy.get('.ant-table-row-level-0').should('contain', 'Ack'); |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases/testCaseIncidentStatus?latest=true&startTs=*&endTs=*&limit=*', |  | ||||||
|         'getIncidentList' |  | ||||||
|       ); |  | ||||||
|       cy.sidebarClick(SidebarItem.INCIDENT_MANAGER); |  | ||||||
| 
 |  | ||||||
|       verifyResponseStatusCode('@getIncidentList', 200); |  | ||||||
| 
 |  | ||||||
|       cy.get(`[data-testid="test-case-${testName}"]`).should('be.visible'); |  | ||||||
|       cy.get(`[data-testid="${testName}-status"]`) |  | ||||||
|         .find(`[data-testid="edit-resolution-icon"]`) |  | ||||||
|         .click(); |  | ||||||
|       cy.get(`[data-testid="test-case-resolution-status-type"]`).click(); |  | ||||||
|       cy.get(`[title="Resolved"]`).click(); |  | ||||||
|       cy.get('#testCaseResolutionStatusDetails_testCaseFailureReason').click(); |  | ||||||
|       cy.get('[title="Missing Data"]').click(); |  | ||||||
|       cy.get('.toastui-editor-md-container > .toastui-editor > .ProseMirror') |  | ||||||
|         .click() |  | ||||||
|         .type('test'); |  | ||||||
|       interceptURL( |  | ||||||
|         'POST', |  | ||||||
|         '/api/v1/dataQuality/testCases/testCaseIncidentStatus', |  | ||||||
|         'updateTestCaseIncidentStatus' |  | ||||||
|       ); |  | ||||||
|       cy.get('.ant-modal-footer').contains('Submit').click(); |  | ||||||
|       verifyResponseStatusCode('@updateTestCaseIncidentStatus', 200); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Task should be closed', () => { |  | ||||||
|       goToProfilerTab(); |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases/name/*?fields=*', |  | ||||||
|         'getTestCase' |  | ||||||
|       ); |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases?fields=*&entityLink=*&includeAllTests=true&limit=*', |  | ||||||
|         'testCaseList' |  | ||||||
|       ); |  | ||||||
|       interceptURL('GET', '/api/v1/feed?entityLink=*&type=Task', 'getTaskFeed'); |  | ||||||
|       cy.get('[data-testid="profiler-tab-left-panel"]') |  | ||||||
|         .contains('Data Quality') |  | ||||||
|         .click(); |  | ||||||
|       verifyResponseStatusCode('@testCaseList', 200); |  | ||||||
|       cy.get(`[data-testid="${testName}"]`) |  | ||||||
|         .find('.last-run-box.failed') |  | ||||||
|         .scrollIntoView() |  | ||||||
|         .should('be.visible'); |  | ||||||
| 
 |  | ||||||
|       cy.get(`[data-testid="${testName}"]`).contains(testName).click(); |  | ||||||
|       verifyResponseStatusCode('@getTestCase', 200); |  | ||||||
|       cy.get('[data-testid="incident"]').click(); |  | ||||||
|       verifyResponseStatusCode('@getTaskFeed', 200); |  | ||||||
|       cy.get('[data-testid="closed-task"]').click(); |  | ||||||
|       cy.get('[data-testid="task-feed-card"]').should('be.visible'); |  | ||||||
|       cy.get('[data-testid="task-tab"]').should( |  | ||||||
|         'contain', |  | ||||||
|         'Resolved the Task.' |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Re-run pipeline', () => { |  | ||||||
|       triggerTestCasePipeline({ |  | ||||||
|         serviceName: DATABASE_SERVICE.service.name, |  | ||||||
|         tableName: TABLE_NAME, |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Verify open and closed task', () => { |  | ||||||
|       acknowledgeTask(testName); |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases/name/*?fields=*', |  | ||||||
|         'getTestCase' |  | ||||||
|       ); |  | ||||||
|       interceptURL('GET', '/api/v1/feed?entityLink=*&type=Task', 'getTaskFeed'); |  | ||||||
|       cy.reload(); |  | ||||||
|       verifyResponseStatusCode('@getTestCase', 200); |  | ||||||
|       cy.get('[data-testid="incident"]').click(); |  | ||||||
|       verifyResponseStatusCode('@getTaskFeed', 200); |  | ||||||
|       cy.get('[data-testid="open-task"]') |  | ||||||
|         .invoke('text') |  | ||||||
|         .then((text) => { |  | ||||||
|           expect(text.trim()).equal('1 Open'); |  | ||||||
|         }); |  | ||||||
|       cy.get('[data-testid="closed-task"]') |  | ||||||
|         .invoke('text') |  | ||||||
|         .then((text) => { |  | ||||||
|           expect(text.trim()).equal('1 Closed'); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('Rerunning pipeline for an open incident', () => { |  | ||||||
|     const testName = testCases[2]; |  | ||||||
| 
 |  | ||||||
|     beforeEach(() => { |  | ||||||
|       cy.login(); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Ack incident and verify open task', () => { |  | ||||||
|       acknowledgeTask(testName); |  | ||||||
|       interceptURL( |  | ||||||
|         'GET', |  | ||||||
|         '/api/v1/dataQuality/testCases/name/*?fields=*', |  | ||||||
|         'getTestCase' |  | ||||||
|       ); |  | ||||||
|       interceptURL('GET', '/api/v1/feed?entityLink=*&type=Task', 'getTaskFeed'); |  | ||||||
|       cy.reload(); |  | ||||||
|       verifyResponseStatusCode('@getTestCase', 200); |  | ||||||
|       cy.get('[data-testid="incident"]').click(); |  | ||||||
|       verifyResponseStatusCode('@getTaskFeed', 200); |  | ||||||
|       cy.get('[data-testid="open-task"]') |  | ||||||
|         .invoke('text') |  | ||||||
|         .then((text) => { |  | ||||||
|           expect(text.trim()).equal('1 Open'); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Assign incident to user', () => { |  | ||||||
|       assignIncident(testName); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('Re-run pipeline', () => { |  | ||||||
|       triggerTestCasePipeline({ |  | ||||||
|         serviceName: DATABASE_SERVICE.service.name, |  | ||||||
|         tableName: TABLE_NAME, |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it("Verify incident's status on DQ page", () => { |  | ||||||
|       goToProfilerTab(); |  | ||||||
| 
 |  | ||||||
|       cy.get('[data-testid="profiler-tab-left-panel"]') |  | ||||||
|         .contains('Data Quality') |  | ||||||
|         .click(); |  | ||||||
|       cy.get(`[data-testid="${testName}"]`) |  | ||||||
|         .find('.last-run-box.failed') |  | ||||||
|         .scrollIntoView() |  | ||||||
|         .should('be.visible'); |  | ||||||
|       cy.get(`[data-testid="${testName}-status"]`).should( |  | ||||||
|         'contain', |  | ||||||
|         'Assigned' |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| }); |  | ||||||
| @ -0,0 +1,394 @@ | |||||||
|  | /* | ||||||
|  |  *  Copyright 2024 Collate. | ||||||
|  |  *  Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  *  you may not use this file except in compliance with the License. | ||||||
|  |  *  You may obtain a copy of the License at | ||||||
|  |  *  http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  *  Unless required by applicable law or agreed to in writing, software | ||||||
|  |  *  distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  *  See the License for the specific language governing permissions and | ||||||
|  |  *  limitations under the License. | ||||||
|  |  */ | ||||||
|  | import test, { expect } from '@playwright/test'; | ||||||
|  | import { SidebarItem } from '../../constant/sidebar'; | ||||||
|  | import { TableClass } from '../../support/entity/TableClass'; | ||||||
|  | import { UserClass } from '../../support/user/UserClass'; | ||||||
|  | import { | ||||||
|  |   createNewPage, | ||||||
|  |   descriptionBox, | ||||||
|  |   getApiContext, | ||||||
|  |   redirectToHomePage, | ||||||
|  | } from '../../utils/common'; | ||||||
|  | import { | ||||||
|  |   acknowledgeTask, | ||||||
|  |   assignIncident, | ||||||
|  |   triggerTestSuitePipelineAndWaitForSuccess, | ||||||
|  |   visitProfilerTab, | ||||||
|  | } from '../../utils/incidentManager'; | ||||||
|  | import { sidebarClick } from '../../utils/sidebar'; | ||||||
|  | 
 | ||||||
|  | const user1 = new UserClass(); | ||||||
|  | const user2 = new UserClass(); | ||||||
|  | const user3 = new UserClass(); | ||||||
|  | const users = [user1, user2, user3]; | ||||||
|  | const table1 = new TableClass(); | ||||||
|  | 
 | ||||||
|  | // use the admin user to login
 | ||||||
|  | test.use({ storageState: 'playwright/.auth/admin.json' }); | ||||||
|  | 
 | ||||||
|  | test.describe.configure({ mode: 'serial' }); | ||||||
|  | 
 | ||||||
|  | test.describe('Incident Manager', () => { | ||||||
|  |   test.beforeAll(async ({ browser }) => { | ||||||
|  |     // since we need to poll for the pipeline status, we need to increase the timeout
 | ||||||
|  |     test.setTimeout(90000); | ||||||
|  | 
 | ||||||
|  |     const { afterAction, apiContext, page } = await createNewPage(browser); | ||||||
|  | 
 | ||||||
|  |     const { pipeline } = await table1.createTestSuiteAndPipelines(apiContext); | ||||||
|  |     for (let i = 0; i < 3; i++) { | ||||||
|  |       await table1.createTestCase(apiContext, { | ||||||
|  |         parameterValues: [ | ||||||
|  |           { name: 'minColValue', value: 12 }, | ||||||
|  |           { name: 'maxColValue', value: 24 }, | ||||||
|  |         ], | ||||||
|  |         testDefinition: 'tableColumnCountToBeBetween', | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     await apiContext.post( | ||||||
|  |       `/api/v1/services/ingestionPipelines/deploy/${pipeline.id}` | ||||||
|  |     ); | ||||||
|  |     await triggerTestSuitePipelineAndWaitForSuccess({ | ||||||
|  |       page, | ||||||
|  |       table: table1, | ||||||
|  |       pipeline: { id: pipeline.id }, | ||||||
|  |       apiContext, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     for (const user of users) { | ||||||
|  |       await user.create(apiContext); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await afterAction(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test.afterAll(async ({ browser }) => { | ||||||
|  |     const { apiContext, afterAction } = await createNewPage(browser); | ||||||
|  |     for (const entity of [...users, table1]) { | ||||||
|  |       await entity.delete(apiContext); | ||||||
|  |     } | ||||||
|  |     await afterAction(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test.slow(true); | ||||||
|  | 
 | ||||||
|  |   test.beforeEach(async ({ page }) => { | ||||||
|  |     await redirectToHomePage(page); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Basic Scenario', async ({ page }) => { | ||||||
|  |     const testCase = table1.testCasesResponseData[0]; | ||||||
|  |     const testCaseName = testCase?.['name']; | ||||||
|  |     const assignee = { | ||||||
|  |       name: user1.data.email.split('@')[0], | ||||||
|  |       displayName: user1.getUserName(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     await test.step("Acknowledge table test case's failure", async () => { | ||||||
|  |       await acknowledgeTask({ | ||||||
|  |         page, | ||||||
|  |         testCase: testCaseName, | ||||||
|  |         table: table1, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step('Assign incident to user', async () => { | ||||||
|  |       await assignIncident({ | ||||||
|  |         page, | ||||||
|  |         testCaseName, | ||||||
|  |         user: assignee, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step('Re-assign incident to user', async () => { | ||||||
|  |       const assignee1 = { | ||||||
|  |         name: user2.data.email.split('@')[0], | ||||||
|  |         displayName: user2.getUserName(), | ||||||
|  |       }; | ||||||
|  |       const testCaseResponse = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases/name/*?fields=*' | ||||||
|  |       ); | ||||||
|  |       await page.click(`[data-testid="test-case-${testCaseName}"]`); | ||||||
|  | 
 | ||||||
|  |       await testCaseResponse; | ||||||
|  | 
 | ||||||
|  |       const incidentDetails = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases/testCaseIncidentStatus/stateId/*' | ||||||
|  |       ); | ||||||
|  |       await page.click('[data-testid="incident"]'); | ||||||
|  |       await incidentDetails; | ||||||
|  | 
 | ||||||
|  |       await page.getByRole('button', { name: 'down' }).click(); | ||||||
|  |       await page.waitForSelector('role=menuitem[name="Reassign"]', { | ||||||
|  |         state: 'visible', | ||||||
|  |       }); | ||||||
|  |       await page.getByRole('menuitem', { name: 'Reassign' }).click(); | ||||||
|  | 
 | ||||||
|  |       const searchUserResponse = page.waitForResponse( | ||||||
|  |         `/api/v1/search/suggest?q=*${user2.data.firstName}*${user2.data.lastName}*&index=user_search_index*` | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       await page.getByTestId('select-assignee').locator('div').click(); | ||||||
|  |       await page.getByLabel('Assignee:').fill(assignee1.displayName); | ||||||
|  |       await searchUserResponse; | ||||||
|  | 
 | ||||||
|  |       await page.click(`[data-testid="${assignee1.name.toLocaleLowerCase()}"]`); | ||||||
|  |       const updateAssignee = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases/testCaseIncidentStatus' | ||||||
|  |       ); | ||||||
|  |       await page.getByRole('button', { name: 'Submit' }).click(); | ||||||
|  | 
 | ||||||
|  |       await updateAssignee; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step( | ||||||
|  |       "Re-assign incident from test case page's header", | ||||||
|  |       async () => { | ||||||
|  |         const assignee2 = { | ||||||
|  |           name: user3.data.email.split('@')[0], | ||||||
|  |           displayName: user3.getUserName(), | ||||||
|  |         }; | ||||||
|  |         const testCaseResponse = page.waitForResponse( | ||||||
|  |           '/api/v1/dataQuality/testCases/name/*?fields=*' | ||||||
|  |         ); | ||||||
|  |         await page.reload(); | ||||||
|  | 
 | ||||||
|  |         await testCaseResponse; | ||||||
|  | 
 | ||||||
|  |         const listUserResponse = page.waitForResponse('/api/v1/users?*'); | ||||||
|  |         await page.click('[data-testid="assignee"] [data-testid="edit-owner"]'); | ||||||
|  |         listUserResponse; | ||||||
|  |         await page.waitForSelector('[data-testid="loader"]', { | ||||||
|  |           state: 'detached', | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         const searchUserResponse = page.waitForResponse( | ||||||
|  |           '/api/v1/search/query?q=*' | ||||||
|  |         ); | ||||||
|  |         await page.fill( | ||||||
|  |           '[data-testid="owner-select-users-search-bar"]', | ||||||
|  |           assignee2.displayName | ||||||
|  |         ); | ||||||
|  |         await searchUserResponse; | ||||||
|  | 
 | ||||||
|  |         const updateIncident = page.waitForResponse( | ||||||
|  |           '/api/v1/dataQuality/testCases/testCaseIncidentStatus' | ||||||
|  |         ); | ||||||
|  |         await page.click(`.ant-popover [title="${assignee2.displayName}"]`); | ||||||
|  |         await updateIncident; | ||||||
|  | 
 | ||||||
|  |         await page.waitForSelector( | ||||||
|  |           '[data-testid="assignee"] [data-testid="owner-link"]' | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         await expect( | ||||||
|  |           page.locator('[data-testid="assignee"] [data-testid="owner-link"]') | ||||||
|  |         ).toContainText(assignee2.displayName); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     await test.step('Resolve incident', async () => { | ||||||
|  |       await page.click('[data-testid="incident"]'); | ||||||
|  |       await page.getByRole('button', { name: 'Resolve' }).click(); | ||||||
|  |       await page.click('#testCaseFailureReason'); | ||||||
|  |       await page.click('[title="Missing Data"]'); | ||||||
|  |       await page.click(descriptionBox); | ||||||
|  |       await page.fill(descriptionBox, 'test'); | ||||||
|  | 
 | ||||||
|  |       const updateIncident = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases/testCaseIncidentStatus' | ||||||
|  |       ); | ||||||
|  |       await page.click('.ant-modal-footer >> text=Submit'); | ||||||
|  |       await updateIncident; | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Resolving incident & re-run pipeline', async ({ page }) => { | ||||||
|  |     const testCase = table1.testCasesResponseData[1]; | ||||||
|  |     const testCaseName = testCase?.['name']; | ||||||
|  |     const pipeline = table1.testSuitePipelineResponseData[0]; | ||||||
|  |     const { apiContext } = await getApiContext(page); | ||||||
|  | 
 | ||||||
|  |     await test.step("Acknowledge table test case's failure", async () => { | ||||||
|  |       await acknowledgeTask({ | ||||||
|  |         page, | ||||||
|  |         testCase: testCaseName, | ||||||
|  |         table: table1, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step('Resolve task from incident list page', async () => { | ||||||
|  |       await visitProfilerTab(page, table1); | ||||||
|  |       const testCaseResponse = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases?fields=*' | ||||||
|  |       ); | ||||||
|  |       await page | ||||||
|  |         .getByTestId('profiler-tab-left-panel') | ||||||
|  |         .getByText('Data Quality') | ||||||
|  |         .click(); | ||||||
|  |       await testCaseResponse; | ||||||
|  | 
 | ||||||
|  |       await expect( | ||||||
|  |         page.locator(`[data-testid="${testCaseName}"] .last-run-box.failed`) | ||||||
|  |       ).toBeVisible(); | ||||||
|  |       await expect(page.getByTestId(`${testCaseName}-status`)).toContainText( | ||||||
|  |         'Ack' | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       const incidentDetailsRes = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases/testCaseIncidentStatus?latest=true&startTs=*&endTs=*&limit=*' | ||||||
|  |       ); | ||||||
|  |       await sidebarClick(page, SidebarItem.INCIDENT_MANAGER); | ||||||
|  |       await incidentDetailsRes; | ||||||
|  | 
 | ||||||
|  |       await expect( | ||||||
|  |         page.locator(`[data-testid="test-case-${testCaseName}"]`) | ||||||
|  |       ).toBeVisible(); | ||||||
|  | 
 | ||||||
|  |       await page.click( | ||||||
|  |         `[data-testid="${testCaseName}-status"] [data-testid="edit-resolution-icon"]` | ||||||
|  |       ); | ||||||
|  |       await page.click(`[data-testid="test-case-resolution-status-type"]`); | ||||||
|  |       await page.click(`[title="Resolved"]`); | ||||||
|  |       await page.click( | ||||||
|  |         '#testCaseResolutionStatusDetails_testCaseFailureReason' | ||||||
|  |       ); | ||||||
|  |       await page.click('[title="Missing Data"]'); | ||||||
|  |       await page.click(descriptionBox); | ||||||
|  |       await page.fill(descriptionBox, 'test'); | ||||||
|  |       const updateTestCaseIncidentStatus = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases/testCaseIncidentStatus' | ||||||
|  |       ); | ||||||
|  |       await page.click('.ant-modal-footer >> text=Submit'); | ||||||
|  |       await updateTestCaseIncidentStatus; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step('Task should be closed', async () => { | ||||||
|  |       await visitProfilerTab(page, table1); | ||||||
|  |       const testCaseResponse = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases?fields=*' | ||||||
|  |       ); | ||||||
|  |       await page | ||||||
|  |         .getByTestId('profiler-tab-left-panel') | ||||||
|  |         .getByText('Data Quality') | ||||||
|  |         .click(); | ||||||
|  |       await testCaseResponse; | ||||||
|  | 
 | ||||||
|  |       await expect( | ||||||
|  |         page.locator(`[data-testid="${testCaseName}"] .last-run-box.failed`) | ||||||
|  |       ).toBeVisible(); | ||||||
|  | 
 | ||||||
|  |       await page.click( | ||||||
|  |         `[data-testid="${testCaseName}"] >> text=${testCaseName}` | ||||||
|  |       ); | ||||||
|  |       await page.click('[data-testid="incident"]'); | ||||||
|  |       await page.click('[data-testid="closed-task"]'); | ||||||
|  |       await page.waitForSelector('[data-testid="task-feed-card"]'); | ||||||
|  | 
 | ||||||
|  |       await expect(page.locator('[data-testid="task-tab"]')).toContainText( | ||||||
|  |         'Resolved the Task.' | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step('Re-run pipeline', async () => { | ||||||
|  |       await triggerTestSuitePipelineAndWaitForSuccess({ | ||||||
|  |         page, | ||||||
|  |         table: table1, | ||||||
|  |         pipeline: { id: pipeline?.['id'] }, | ||||||
|  |         apiContext, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step('Verify open and closed task', async () => { | ||||||
|  |       await acknowledgeTask({ | ||||||
|  |         page, | ||||||
|  |         testCase: testCaseName, | ||||||
|  |         table: table1, | ||||||
|  |       }); | ||||||
|  |       await page.reload(); | ||||||
|  | 
 | ||||||
|  |       await page.click('[data-testid="incident"]'); | ||||||
|  | 
 | ||||||
|  |       await expect(page.locator(`[data-testid="open-task"]`)).toHaveText( | ||||||
|  |         '1 Open' | ||||||
|  |       ); | ||||||
|  |       await expect(page.locator(`[data-testid="closed-task"]`)).toHaveText( | ||||||
|  |         '1 Closed' | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Rerunning pipeline for an open incident', async ({ page }) => { | ||||||
|  |     const testCase = table1.testCasesResponseData[2]; | ||||||
|  |     const testCaseName = testCase?.['name']; | ||||||
|  |     const pipeline = table1.testSuitePipelineResponseData[0]; | ||||||
|  |     const assignee = { | ||||||
|  |       name: user1.data.email.split('@')[0], | ||||||
|  |       displayName: user1.getUserName(), | ||||||
|  |     }; | ||||||
|  |     const { apiContext } = await getApiContext(page); | ||||||
|  | 
 | ||||||
|  |     await test.step('Ack incident and verify open task', async () => { | ||||||
|  |       await acknowledgeTask({ | ||||||
|  |         page, | ||||||
|  |         testCase: testCaseName, | ||||||
|  |         table: table1, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       await page.reload(); | ||||||
|  | 
 | ||||||
|  |       await page.click('[data-testid="incident"]'); | ||||||
|  | 
 | ||||||
|  |       await expect(page.locator(`[data-testid="open-task"]`)).toHaveText( | ||||||
|  |         '1 Open' | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step('Assign incident to user', async () => { | ||||||
|  |       await assignIncident({ | ||||||
|  |         page, | ||||||
|  |         testCaseName, | ||||||
|  |         user: assignee, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step('Re-run pipeline', async () => { | ||||||
|  |       await triggerTestSuitePipelineAndWaitForSuccess({ | ||||||
|  |         page, | ||||||
|  |         table: table1, | ||||||
|  |         pipeline: { id: pipeline?.['id'] }, | ||||||
|  |         apiContext, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await test.step("Verify incident's status on DQ page", async () => { | ||||||
|  |       await visitProfilerTab(page, table1); | ||||||
|  |       const testCaseResponse = page.waitForResponse( | ||||||
|  |         '/api/v1/dataQuality/testCases?fields=*' | ||||||
|  |       ); | ||||||
|  |       await page | ||||||
|  |         .getByTestId('profiler-tab-left-panel') | ||||||
|  |         .getByText('Data Quality') | ||||||
|  |         .click(); | ||||||
|  |       await testCaseResponse; | ||||||
|  | 
 | ||||||
|  |       await expect( | ||||||
|  |         page.locator(`[data-testid="${testCaseName}"] .last-run-box.failed`) | ||||||
|  |       ).toBeVisible(); | ||||||
|  |       await expect(page.getByTestId(`${testCaseName}-status`)).toContainText( | ||||||
|  |         'Assigned' | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @ -65,3 +65,8 @@ export enum ENTITY_PATH { | |||||||
|   'apiEndpoints' = 'apiEndpoint', |   'apiEndpoints' = 'apiEndpoint', | ||||||
|   'dataProducts' = 'dataProduct', |   'dataProducts' = 'dataProduct', | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export type TestCaseData = { | ||||||
|  |   parameterValues: unknown[]; | ||||||
|  |   testDefinition: string; | ||||||
|  | }; | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ import { APIRequestContext, Page } from '@playwright/test'; | |||||||
| import { SERVICE_TYPE } from '../../constant/service'; | import { SERVICE_TYPE } from '../../constant/service'; | ||||||
| import { uuid } from '../../utils/common'; | import { uuid } from '../../utils/common'; | ||||||
| import { visitEntityPage } from '../../utils/entity'; | import { visitEntityPage } from '../../utils/entity'; | ||||||
| import { EntityTypeEndpoint } from './Entity.interface'; | import { EntityTypeEndpoint, TestCaseData } from './Entity.interface'; | ||||||
| import { EntityClass } from './EntityClass'; | import { EntityClass } from './EntityClass'; | ||||||
| 
 | 
 | ||||||
| export class TableClass extends EntityClass { | export class TableClass extends EntityClass { | ||||||
| @ -169,7 +169,7 @@ export class TableClass extends EntityClass { | |||||||
| 
 | 
 | ||||||
|   async createTestSuiteAndPipelines(apiContext: APIRequestContext) { |   async createTestSuiteAndPipelines(apiContext: APIRequestContext) { | ||||||
|     if (!this.entityResponseData) { |     if (!this.entityResponseData) { | ||||||
|       return this.create(apiContext); |       await this.create(apiContext); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const testSuiteData = await apiContext |     const testSuiteData = await apiContext | ||||||
| @ -177,7 +177,7 @@ export class TableClass extends EntityClass { | |||||||
|         data: { |         data: { | ||||||
|           name: `pw-test-suite-${uuid()}`, |           name: `pw-test-suite-${uuid()}`, | ||||||
|           executableEntityReference: |           executableEntityReference: | ||||||
|             this.entityResponseData['fullyQualifiedName'], |             this.entityResponseData?.['fullyQualifiedName'], | ||||||
|           description: 'Playwright test suite for table', |           description: 'Playwright test suite for table', | ||||||
|         }, |         }, | ||||||
|       }) |       }) | ||||||
| @ -221,12 +221,16 @@ export class TableClass extends EntityClass { | |||||||
|         }, |         }, | ||||||
|       }) |       }) | ||||||
|       .then((res) => res.json()); |       .then((res) => res.json()); | ||||||
|  | 
 | ||||||
|     this.testSuitePipelineResponseData.push(pipelineData); |     this.testSuitePipelineResponseData.push(pipelineData); | ||||||
| 
 | 
 | ||||||
|     return pipelineData; |     return pipelineData; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async createTestCase(apiContext: APIRequestContext) { |   async createTestCase( | ||||||
|  |     apiContext: APIRequestContext, | ||||||
|  |     testCaseData?: TestCaseData | ||||||
|  |   ) { | ||||||
|     if (!this.testSuiteResponseData) { |     if (!this.testSuiteResponseData) { | ||||||
|       await this.createTestSuiteAndPipelines(apiContext); |       await this.createTestSuiteAndPipelines(apiContext); | ||||||
|     } |     } | ||||||
| @ -236,9 +240,10 @@ export class TableClass extends EntityClass { | |||||||
|         data: { |         data: { | ||||||
|           name: `pw-test-case-${uuid()}`, |           name: `pw-test-case-${uuid()}`, | ||||||
|           entityLink: `<#E::table::${this.entityResponseData?.['fullyQualifiedName']}>`, |           entityLink: `<#E::table::${this.entityResponseData?.['fullyQualifiedName']}>`, | ||||||
|           testDefinition: 'tableRowCountToBeBetween', |           testDefinition: | ||||||
|  |             testCaseData?.testDefinition ?? 'tableRowCountToBeBetween', | ||||||
|           testSuite: this.testSuiteResponseData?.['fullyQualifiedName'], |           testSuite: this.testSuiteResponseData?.['fullyQualifiedName'], | ||||||
|           parameterValues: [ |           parameterValues: testCaseData?.parameterValues ?? [ | ||||||
|             { name: 'minValue', value: 12 }, |             { name: 'minValue', value: 12 }, | ||||||
|             { name: 'maxValue', value: 34 }, |             { name: 'maxValue', value: 34 }, | ||||||
|           ], |           ], | ||||||
|  | |||||||
| @ -0,0 +1,128 @@ | |||||||
|  | /* | ||||||
|  |  *  Copyright 2024 Collate. | ||||||
|  |  *  Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  *  you may not use this file except in compliance with the License. | ||||||
|  |  *  You may obtain a copy of the License at | ||||||
|  |  *  http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  *  Unless required by applicable law or agreed to in writing, software | ||||||
|  |  *  distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  *  See the License for the specific language governing permissions and | ||||||
|  |  *  limitations under the License. | ||||||
|  |  */ | ||||||
|  | import { APIRequestContext, expect, Page } from '@playwright/test'; | ||||||
|  | import { SidebarItem } from '../constant/sidebar'; | ||||||
|  | import { TableClass } from '../support/entity/TableClass'; | ||||||
|  | import { redirectToHomePage } from './common'; | ||||||
|  | import { sidebarClick } from './sidebar'; | ||||||
|  | 
 | ||||||
|  | export const visitProfilerTab = async (page: Page, table: TableClass) => { | ||||||
|  |   await redirectToHomePage(page); | ||||||
|  |   await table.visitEntityPage(page); | ||||||
|  |   await page.click('[data-testid="profiler"]'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const acknowledgeTask = async (data: { | ||||||
|  |   testCase: string; | ||||||
|  |   page: Page; | ||||||
|  |   table: TableClass; | ||||||
|  | }) => { | ||||||
|  |   const { testCase, page, table } = data; | ||||||
|  |   await visitProfilerTab(page, table); | ||||||
|  |   await page.click('[data-testid="profiler-tab-left-panel"]'); | ||||||
|  |   await page | ||||||
|  |     .getByTestId('profiler-tab-left-panel') | ||||||
|  |     .getByText('Data Quality') | ||||||
|  |     .click(); | ||||||
|  |   await page.click(`[data-testid="${testCase}"] >> .last-run-box.failed`); | ||||||
|  |   await page.waitForSelector(`[data-testid="${testCase}-status"] >> text=New`); | ||||||
|  |   await page.click(`[data-testid="${testCase}"] >> text=${testCase}`); | ||||||
|  |   await page.click('[data-testid="edit-resolution-icon"]'); | ||||||
|  |   await page.click('[data-testid="test-case-resolution-status-type"]'); | ||||||
|  |   await page.click('[title="Ack"]'); | ||||||
|  |   const statusChangeResponse = page.waitForResponse( | ||||||
|  |     '/api/v1/dataQuality/testCases/testCaseIncidentStatus' | ||||||
|  |   ); | ||||||
|  |   await page.click('#update-status-button'); | ||||||
|  |   await statusChangeResponse; | ||||||
|  |   await page.waitForSelector(`[data-testid="${testCase}-status"] >> text=Ack`); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const assignIncident = async (data: { | ||||||
|  |   testCaseName: string; | ||||||
|  |   page: Page; | ||||||
|  |   user: { name: string; displayName: string }; | ||||||
|  | }) => { | ||||||
|  |   const { testCaseName, page, user } = data; | ||||||
|  |   await sidebarClick(page, SidebarItem.INCIDENT_MANAGER); | ||||||
|  |   await page.waitForSelector(`[data-testid="test-case-${testCaseName}"]`); | ||||||
|  |   await page.click( | ||||||
|  |     `[data-testid="${testCaseName}-status"] [data-testid="edit-resolution-icon"]` | ||||||
|  |   ); | ||||||
|  |   await page.click('[data-testid="test-case-resolution-status-type"]'); | ||||||
|  |   await page.click('[title="Assigned"]'); | ||||||
|  |   await page.waitForSelector('#testCaseResolutionStatusDetails_assignee'); | ||||||
|  |   await page.fill( | ||||||
|  |     '#testCaseResolutionStatusDetails_assignee', | ||||||
|  |     user.displayName | ||||||
|  |   ); | ||||||
|  |   await page.waitForResponse('/api/v1/search/suggest?q=*'); | ||||||
|  |   await page.click(`[data-testid="${user.name.toLocaleLowerCase()}"]`); | ||||||
|  |   const updateIncident = page.waitForResponse( | ||||||
|  |     '/api/v1/dataQuality/testCases/testCaseIncidentStatus' | ||||||
|  |   ); | ||||||
|  |   await page.click('#update-status-button'); | ||||||
|  |   await updateIncident; | ||||||
|  |   await page.waitForSelector( | ||||||
|  |     `[data-testid="${testCaseName}-status"] [data-testid="badge-container"] >> text=Assigned` | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   await expect( | ||||||
|  |     page.locator( | ||||||
|  |       `[data-testid="${testCaseName}-status"] [data-testid="badge-container"]` | ||||||
|  |     ) | ||||||
|  |   ).toContainText('Assigned'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const triggerTestSuitePipelineAndWaitForSuccess = async (data: { | ||||||
|  |   page: Page; | ||||||
|  |   apiContext: APIRequestContext; | ||||||
|  |   table: TableClass; | ||||||
|  |   pipeline: { id: string }; | ||||||
|  | }) => { | ||||||
|  |   const { page, apiContext, table, pipeline } = data; | ||||||
|  |   // wait for 2s before the pipeline to be run
 | ||||||
|  |   await page.waitForTimeout(2000); | ||||||
|  |   await apiContext | ||||||
|  |     .post(`/api/v1/services/ingestionPipelines/trigger/${pipeline.id}`) | ||||||
|  |     .then((res) => { | ||||||
|  |       if (res.status() !== 200) { | ||||||
|  |         return apiContext.post( | ||||||
|  |           `/api/v1/services/ingestionPipelines/trigger/${pipeline.id}` | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |   // Wait for the run to complete
 | ||||||
|  |   await page.waitForTimeout(2000); | ||||||
|  | 
 | ||||||
|  |   await expect | ||||||
|  |     .poll( | ||||||
|  |       async () => { | ||||||
|  |         const response = await apiContext | ||||||
|  |           .get( | ||||||
|  |             `/api/v1/services/ingestionPipelines?fields=pipelineStatuses&testSuite=${table.testSuiteResponseData?.['fullyQualifiedName']}&pipelineType=TestSuite` | ||||||
|  |           ) | ||||||
|  |           .then((res) => res.json()); | ||||||
|  | 
 | ||||||
|  |         return response.data?.[0]?.pipelineStatuses?.pipelineState; | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         // Custom expect message for reporting, optional.
 | ||||||
|  |         message: 'Wait for the pipeline to be successful', | ||||||
|  |         timeout: 60_000, | ||||||
|  |         intervals: [5_000, 10_000], | ||||||
|  |       } | ||||||
|  |     ) | ||||||
|  |     .toBe('success'); | ||||||
|  | }; | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Shailesh Parmar
						Shailesh Parmar