From 6358446c0e25f90ae6071bc52fe61da1371e90c0 Mon Sep 17 00:00:00 2001 From: Pranita Fulsundar Date: Wed, 6 Aug 2025 22:57:25 +0530 Subject: [PATCH] test(ui): enhance data contract playwright test (#22795) * enhance data contract test * minor fix --- .../ui/playwright/constant/dataContracts.ts | 10 +- .../e2e/Pages/DataContracts.spec.ts | 256 ++++++++++-------- .../ui/playwright/utils/dataContracts.ts | 61 +++++ .../main/resources/ui/src/rest/contractAPI.ts | 2 +- 4 files changed, 216 insertions(+), 113 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/dataContracts.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/dataContracts.ts index 00e05abc1ab..13be66fd5f9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/constant/dataContracts.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/dataContracts.ts @@ -44,9 +44,9 @@ export const DATA_CONTRACT_SEMANTICS2 = { }; export const NEW_TABLE_TEST_CASE = { - name: `table_column_name_to_exist_in_id_${uuid()}`, - label: 'Table Column Name To Exist', - type: 'tableColumnNameToExist', - field: 'testCase', - description: 'New table test case for TableColumnNameToExist', + name: `table_row_count_to_equal_in_id_${uuid()}`, + label: 'Table Row Count To Equal', + type: 'tableRowCountToEqual', + value: '100000', + description: 'New table test case for TableRowCountToEqual', }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts index 491a4f1f576..5d26745b7c2 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts @@ -31,6 +31,10 @@ import { redirectToHomePage, toastNotification, } from '../../utils/common'; +import { + saveAndTriggerDataContractValidation, + validateDataContractInsideBundleTestSuites, +} from '../../utils/dataContracts'; import { addOwner } from '../../utils/entity'; test.use({ storageState: 'playwright/.auth/admin.json' }); @@ -230,90 +234,9 @@ test.describe('Data Contracts', () => { ).not.toBeVisible(); }); - await test.step('Fill Contract Quality form', async () => { - await page.getByRole('button', { name: 'Quality' }).click(); - - // Fill Contract Quality form - - await page.getByTestId('add-test-button').click(); - - await expect(page.getByRole('dialog')).toBeVisible(); - - await page.fill( - '[data-testid="test-case-name"]', - NEW_TABLE_TEST_CASE.name - ); - - await page.click('#testCaseFormV1_testTypeId'); - await page.waitForSelector(`text=${NEW_TABLE_TEST_CASE.label}`); - await page.click(`text=${NEW_TABLE_TEST_CASE.label}`); - await page.fill( - '#testCaseFormV1_params_columnName', - NEW_TABLE_TEST_CASE.field - ); - - await page.click('[data-testid="tags-selector"] input'); - await page.fill('[data-testid="tags-selector"] input', testTag.data.name); - await page - .getByTestId(`tag-${testTag.responseData.fullyQualifiedName}`) - .click(); - - await clickOutside(page); - - await page.click('[data-testid="glossary-terms-selector"] input'); - await page.fill( - '[data-testid="glossary-terms-selector"] input', - testGlossaryTerm.data.name - ); - - await page - .getByTestId(`tag-${testGlossaryTerm.responseData.fullyQualifiedName}`) - .click(); - - await clickOutside(page); - - const testCaseResponse = page.waitForResponse( - '/api/v1/dataQuality/testCases' - ); - await page.click('[data-testid="create-btn"]'); - await testCaseResponse; - - await page.waitForTimeout(100); - - await expect( - page - .locator('.ant-table-cell') - .filter({ hasText: NEW_TABLE_TEST_CASE.name }) - ).toBeVisible(); - }); - - await test.step('Save contract and validate', async () => { - const saveContractResponse = page.waitForResponse( - '/api/v1/dataContracts' - ); - await page.getByTestId('save-contract-btn').click(); - await saveContractResponse; - - await toastNotification(page, 'Data contract saved successfully'); - - await expect( - page - .getByTestId('contract-card-title-container') - .filter({ hasText: 'Contract Status' }) - ).not.toBeVisible(); - - const runNowResponse = page.waitForResponse( - '/api/v1/dataContracts/*/validate' - ); - await page.getByTestId('contract-run-now-button').click(); - await runNowResponse; - - await toastNotification( - page, - 'Contract validation trigger successfully.' - ); - - await page.reload(); + await test.step('Save contract and validate for semantics', async () => { + // save and trigger contract validation + await saveAndTriggerDataContractValidation(page); await expect( page.getByTestId('contract-card-title-container').filter({ @@ -335,6 +258,10 @@ test.describe('Data Contracts', () => { dataTestId: 'data-assets-header', }); + const runNowResponse = page.waitForResponse( + '/api/v1/dataContracts/*/validate' + ); + await page.getByTestId('contract-run-now-button').click(); await runNowResponse; @@ -350,38 +277,153 @@ test.describe('Data Contracts', () => { ).toContainText('Passed'); }); - await test.step('Edit contract and validate', async () => { - await page.getByTestId('contract-edit-button').click(); + await test.step( + 'Add table test case and validate for quality', + async () => { + await page.getByTestId('contract-edit-button').click(); - await page.getByRole('tab', { name: 'Quality' }).click(); + await page.getByRole('tab', { name: 'Quality' }).click(); - await page - .locator('input[type="checkbox"][aria-label="Select all"]') - .check(); + await page.getByTestId('add-test-button').click(); - await expect( - page.getByRole('checkbox', { name: 'Select all' }) - ).toBeChecked(); + await expect(page.getByRole('dialog')).toBeVisible(); - await page.getByTestId('save-contract-btn').click(); + await page.fill( + '[data-testid="test-case-name"]', + NEW_TABLE_TEST_CASE.name + ); - await toastNotification(page, 'Data contract saved successfully'); + await page.locator('#testCaseFormV1_testTypeId').click(); - const runNowResponse = page.waitForResponse( - '/api/v1/dataContracts/*/validate' - ); - await page.getByTestId('contract-run-now-button').click(); - await runNowResponse; + const dropdown = page.locator('.rc-virtual-list-holder-inner'); - await toastNotification( - page, - 'Contract validation trigger successfully.' - ); + await expect(dropdown).toBeVisible(); - await page.reload(); - }); + for (let i = 0; i < 20; i++) { + const optionVisible = await dropdown + .getByText(NEW_TABLE_TEST_CASE.label) + .isVisible(); + if (optionVisible) { + break; + } + await dropdown.press('ArrowDown'); + } + + await dropdown.getByText(NEW_TABLE_TEST_CASE.label).click(); + + await page.click(`text=${NEW_TABLE_TEST_CASE.label}`); + await page.fill( + '#testCaseFormV1_params_value', + NEW_TABLE_TEST_CASE.value + ); + + await page.click('[data-testid="tags-selector"] input'); + await page.fill( + '[data-testid="tags-selector"] input', + testTag.data.name + ); + await page + .getByTestId(`tag-${testTag.responseData.fullyQualifiedName}`) + .click(); + + await clickOutside(page); + + await page.click('[data-testid="glossary-terms-selector"] input'); + await page.fill( + '[data-testid="glossary-terms-selector"] input', + testGlossaryTerm.data.name + ); + + await page + .getByTestId( + `tag-${testGlossaryTerm.responseData.fullyQualifiedName}` + ) + .click(); + + await clickOutside(page); + + const testCaseResponse = page.waitForResponse( + '/api/v1/dataQuality/testCases' + ); + await page.click('[data-testid="create-btn"]'); + await testCaseResponse; + + await page.waitForTimeout(100); + + await expect( + page + .locator('.ant-table-cell') + .filter({ hasText: NEW_TABLE_TEST_CASE.name }) + ).toBeVisible(); + + await page + .locator('input[type="checkbox"][aria-label="Select all"]') + .check(); + + await expect( + page.getByRole('checkbox', { name: 'Select all' }) + ).toBeChecked(); + + // save and trigger contract validation + await saveAndTriggerDataContractValidation(page); + + await expect(page.getByTestId('alert-bar')).toBeVisible(); + await expect( + page.locator('.anticon-exclamation-circle[role="img"]') + ).toBeVisible(); + } + ); + + await test.step( + 'Validate inside the Observability, bundle test suites, that data contract test suite is present', + async () => { + await validateDataContractInsideBundleTestSuites(page); + + await expect( + page + .getByTestId('test-suite-table') + .locator('.ant-table-cell') + .filter({ + hasText: `${DATA_CONTRACT_DETAILS.name} - Data Contract Expectations`, + }) + ).toBeVisible(); + } + ); + + await test.step( + 'Edit quality expectations from the data contract and validate', + async () => { + await table.visitEntityPage(page); + + await page.getByTestId('contract').click(); + + await page.getByTestId('contract-edit-button').click(); + + await page.getByRole('tab', { name: 'Quality' }).click(); + + await page + .locator('input[type="checkbox"][aria-label="Select all"]') + .uncheck(); + + await expect( + page.getByRole('checkbox', { name: 'Select all' }) + ).not.toBeChecked(); + + await saveAndTriggerDataContractValidation(page); + + await expect( + page.getByTestId('contract-status-card-item-Quality Status') + ).not.toBeVisible(); + } + ); + + // TODO: Add a step to validate the test suite is removed from observability -> bundle test suites await test.step('Verify YAML view', async () => { + await table.visitEntityPage(page); + + await page.getByTestId('contract').click(); + await page.getByTestId('contract-view-switch-tab-yaml').click(); await expect(page.getByTestId('code-mirror-container')).toBeVisible(); @@ -403,7 +445,7 @@ test.describe('Data Contracts', () => { await test.step('Delete contract', async () => { const deleteContractResponse = page.waitForResponse( - 'api/v1/dataContracts/*?hardDelete=true' + 'api/v1/dataContracts/*?hardDelete=true&recursive=true' ); await page.getByTestId('delete-contract-button').click(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts new file mode 100644 index 00000000000..c51a29b53fd --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2025 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 { expect, Page } from '@playwright/test'; +import { SidebarItem } from '../constant/sidebar'; +import { toastNotification } from './common'; +import { sidebarClick } from './sidebar'; + +export const saveAndTriggerDataContractValidation = async (page: Page) => { + const saveContractResponse = page.waitForResponse('/api/v1/dataContracts'); + await page.getByTestId('save-contract-btn').click(); + await saveContractResponse; + + await toastNotification(page, 'Data contract saved successfully'); + + await expect( + page + .getByTestId('contract-card-title-container') + .filter({ hasText: 'Contract Status' }) + ).not.toBeVisible(); + + const runNowResponse = page.waitForResponse( + '/api/v1/dataContracts/*/validate' + ); + await page.getByTestId('contract-run-now-button').click(); + await runNowResponse; + + await toastNotification(page, 'Contract validation trigger successfully.'); + + await page.reload(); +}; + +export const validateDataContractInsideBundleTestSuites = async ( + page: Page +) => { + await sidebarClick(page, SidebarItem.DATA_QUALITY); + + const testSuiteResponse = page.waitForResponse( + '/api/v1/dataQuality/testSuites/search/list?*' + ); + await page.getByTestId('test-suites').click(); + await testSuiteResponse; + + await page.waitForLoadState('networkidle'); + + await page + .locator('.ant-radio-button-wrapper') + .filter({ hasText: 'Bundle Suites' }) + .click(); + + await expect(page.getByTestId('test-suite-table')).toBeVisible(); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/contractAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/contractAPI.ts index 6baabfeb05b..623dc875b90 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/contractAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/contractAPI.ts @@ -98,7 +98,7 @@ export const validateContractById = async (contractId: string) => { export const deleteContractById = async (contractId: string) => { const response = await APIClient.delete( - `/dataContracts/${contractId}?hardDelete=true` + `/dataContracts/${contractId}?hardDelete=true&recursive=true` ); return response.data;