mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-24 17:08:28 +00:00
Migrate: Data quality and profiler test to playwright (#17705)
* Migrate: Data quality and profiler test to playwright * migrate profiler ingestion * migrated test cases to playwright * migrate filter test from data quality * migrate domain filter test
This commit is contained in:
parent
6a2eefbb46
commit
097b87e2db
2
.gitignore
vendored
2
.gitignore
vendored
@ -87,7 +87,7 @@ openmetadata-ui/src/main/resources/ui/cypress/fixtures
|
||||
# Playwright artifacts
|
||||
openmetadata-ui/src/main/resources/ui/playwright/output/
|
||||
openmetadata-ui/src/main/resources/ui/playwright/e2e/.cache/
|
||||
openmetadata-ui/src/main/resources/ui/playwright/.env
|
||||
openmetadata-ui/src/main/resources/ui/.env
|
||||
openmetadata-ui/src/main/resources/ui/playwright/.auth
|
||||
|
||||
#UI - Dereferenced Schemas
|
||||
|
@ -263,6 +263,8 @@ overrides:
|
||||
- off
|
||||
jest/no-standalone-expect:
|
||||
- off
|
||||
jest/no-conditional-expect:
|
||||
- off
|
||||
|
||||
# i18next rule is not required for js, jsx, json and test file
|
||||
- files:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,8 @@ export const POSTGRES = {
|
||||
tableName: 'order_items',
|
||||
};
|
||||
|
||||
export const MYSQL = 'Mysql';
|
||||
|
||||
export const HTTP_CONFIG_SOURCE = {
|
||||
DBT_CATALOG_HTTP_PATH:
|
||||
'https://raw.githubusercontent.com/OnkarVO7/dbt_git_test/dbt_aut/catalog.json',
|
||||
|
@ -0,0 +1,937 @@
|
||||
/*
|
||||
* 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 { expect, Page, test } from '@playwright/test';
|
||||
import { getCurrentMillis } from '../../../src/utils/date-time/DateTimeUtils';
|
||||
import { SidebarItem } from '../../constant/sidebar';
|
||||
import { Domain } from '../../support/domain/Domain';
|
||||
import { TableClass } from '../../support/entity/TableClass';
|
||||
import {
|
||||
assignDomain,
|
||||
clickOutside,
|
||||
createNewPage,
|
||||
descriptionBox,
|
||||
getApiContext,
|
||||
redirectToHomePage,
|
||||
toastNotification,
|
||||
uuid,
|
||||
} from '../../utils/common';
|
||||
import { visitEntityPage } from '../../utils/entity';
|
||||
import { sidebarClick } from '../../utils/sidebar';
|
||||
import { deleteTestCase, visitDataQualityTab } from '../../utils/testCases';
|
||||
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
const table1 = new TableClass();
|
||||
const table2 = new TableClass();
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
await table1.create(apiContext);
|
||||
await table2.create(apiContext);
|
||||
const { testSuiteData } = await table2.createTestSuiteAndPipelines(
|
||||
apiContext
|
||||
);
|
||||
await table2.createTestCase(apiContext, {
|
||||
name: `email_column_values_to_be_in_set_${uuid()}`,
|
||||
entityLink: `<#E::table::${table2.entityResponseData?.['fullyQualifiedName']}::columns::email>`,
|
||||
parameterValues: [
|
||||
{ name: 'allowedValues', value: '["gmail","yahoo","collate"]' },
|
||||
],
|
||||
testDefinition: 'columnValuesToBeInSet',
|
||||
testSuite: testSuiteData?.['fullyQualifiedName'],
|
||||
});
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.afterAll(async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
await table1.delete(apiContext);
|
||||
await table2.delete(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await redirectToHomePage(page);
|
||||
});
|
||||
|
||||
test('Table test case', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
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',
|
||||
};
|
||||
await visitDataQualityTab(page, table1);
|
||||
|
||||
await page.click('[data-testid="profiler-add-table-test-btn"]');
|
||||
await page.click('[data-testid="table"]');
|
||||
|
||||
await test.step('Create', async () => {
|
||||
await page.click('#tableTestForm_testTypeId');
|
||||
await page.waitForSelector(`text=${NEW_TABLE_TEST_CASE.label}`);
|
||||
await page.click(`text=${NEW_TABLE_TEST_CASE.label}`);
|
||||
await page.fill('#tableTestForm_testName', NEW_TABLE_TEST_CASE.name);
|
||||
await page.fill(
|
||||
'#tableTestForm_params_columnName',
|
||||
NEW_TABLE_TEST_CASE.field
|
||||
);
|
||||
await page.fill(descriptionBox, NEW_TABLE_TEST_CASE.description);
|
||||
await page.click('[data-testid="submit-test"]');
|
||||
|
||||
await page.waitForSelector('[data-testid="success-line"]');
|
||||
|
||||
await expect(page.locator('[data-testid="success-line"]')).toBeVisible();
|
||||
|
||||
await page.waitForSelector('[data-testid="add-ingestion-button"]');
|
||||
await page.click('[data-testid="add-ingestion-button"]');
|
||||
await page.click('[data-testid="select-all-test-cases"]');
|
||||
|
||||
// Schedule & Deploy
|
||||
await page.click('[data-testid="cron-type"]');
|
||||
await page.waitForSelector('.ant-select-item-option-content');
|
||||
await page.click('.ant-select-item-option-content:has-text("Hour")');
|
||||
const ingestionPipelines = page.waitForResponse(
|
||||
'/api/v1/services/ingestionPipelines'
|
||||
);
|
||||
const deploy = page.waitForResponse(
|
||||
'/api/v1/services/ingestionPipelines/deploy/*'
|
||||
);
|
||||
const status = page.waitForResponse(
|
||||
'/api/v1/services/ingestionPipelines/status'
|
||||
);
|
||||
await page.click('[data-testid="deploy-button"]');
|
||||
await ingestionPipelines;
|
||||
await deploy;
|
||||
await status;
|
||||
|
||||
// check success
|
||||
await page.waitForSelector('[data-testid="success-line"]', {
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
await expect(page.locator('[data-testid="success-line"]')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('has been created and deployed successfully')
|
||||
).toBeVisible();
|
||||
|
||||
const testCaseResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases?fields=*'
|
||||
);
|
||||
await page.click(`[data-testid="view-service-button"]`);
|
||||
await testCaseResponse;
|
||||
|
||||
await expect(page.getByTestId(NEW_TABLE_TEST_CASE.name)).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Edit', async () => {
|
||||
await page.click(`[data-testid="edit-${NEW_TABLE_TEST_CASE.name}"]`);
|
||||
await page.waitForSelector('.ant-modal-title');
|
||||
await page.locator('#tableTestForm_params_columnName').clear();
|
||||
await page.fill('#tableTestForm_params_columnName', 'new_column_name');
|
||||
const updateTestCaseResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/*'
|
||||
);
|
||||
await page.locator('button').filter({ hasText: 'Submit' }).click();
|
||||
await updateTestCaseResponse;
|
||||
await toastNotification(page, 'Test case updated successfully.');
|
||||
await page.click(`[data-testid="edit-${NEW_TABLE_TEST_CASE.name}"]`);
|
||||
|
||||
await page.waitForSelector('#tableTestForm_params_columnName');
|
||||
|
||||
await expect(page.locator('#tableTestForm_params_columnName')).toHaveValue(
|
||||
'new_column_name'
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
|
||||
await test.step('Delete', async () => {
|
||||
await deleteTestCase(page, NEW_TABLE_TEST_CASE.name);
|
||||
});
|
||||
});
|
||||
|
||||
test('Column test case', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
const NEW_COLUMN_TEST_CASE = {
|
||||
name: 'email_column_value_lengths_to_be_between',
|
||||
column: 'email',
|
||||
type: 'columnValueLengthsToBeBetween',
|
||||
label: 'Column Value Lengths To Be Between',
|
||||
min: '3',
|
||||
max: '6',
|
||||
description: 'New table test case for columnValueLengthsToBeBetween',
|
||||
};
|
||||
|
||||
await visitDataQualityTab(page, table1);
|
||||
await page.click('[data-testid="profiler-add-table-test-btn"]');
|
||||
await page.click('[data-testid="column"]');
|
||||
|
||||
await test.step('Create', async () => {
|
||||
const testDefinitionResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testDefinitions?limit=*&entityType=COLUMN&testPlatform=OpenMetadata&supportedDataType=VARCHAR'
|
||||
);
|
||||
await page.click('#tableTestForm_column');
|
||||
await page.click(`[title="${NEW_COLUMN_TEST_CASE.column}"]`);
|
||||
await testDefinitionResponse;
|
||||
await page.fill('#tableTestForm_testName', NEW_COLUMN_TEST_CASE.name);
|
||||
await page.click('#tableTestForm_testTypeId');
|
||||
await page.click(`[title="${NEW_COLUMN_TEST_CASE.label}"]`);
|
||||
await page.fill(
|
||||
'#tableTestForm_params_minLength',
|
||||
NEW_COLUMN_TEST_CASE.min
|
||||
);
|
||||
await page.fill(
|
||||
'#tableTestForm_params_maxLength',
|
||||
NEW_COLUMN_TEST_CASE.max
|
||||
);
|
||||
await page.fill(descriptionBox, NEW_COLUMN_TEST_CASE.description);
|
||||
|
||||
await page.click('[data-testid="submit-test"]');
|
||||
await page.waitForSelector('[data-testid="success-line"]');
|
||||
|
||||
await expect(page.locator('[data-testid="success-line"]')).toBeVisible();
|
||||
|
||||
await page.waitForSelector('[data-testid="view-service-button"]');
|
||||
|
||||
const testCaseResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases?fields=*'
|
||||
);
|
||||
await page.click(`[data-testid="view-service-button"]`);
|
||||
await testCaseResponse;
|
||||
|
||||
await page.waitForSelector(`[data-testid="${NEW_COLUMN_TEST_CASE.name}"]`);
|
||||
|
||||
await expect(
|
||||
page.locator(`[data-testid="${NEW_COLUMN_TEST_CASE.name}"]`)
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Edit', async () => {
|
||||
await page.click(`[data-testid="edit-${NEW_COLUMN_TEST_CASE.name}"]`);
|
||||
await page.waitForSelector('#tableTestForm_params_minLength');
|
||||
await page.locator('#tableTestForm_params_minLength').clear();
|
||||
await page.fill('#tableTestForm_params_minLength', '4');
|
||||
const updateTestCaseResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/*'
|
||||
);
|
||||
await page.locator('button').getByText('Submit').click();
|
||||
await updateTestCaseResponse;
|
||||
await toastNotification(page, 'Test case updated successfully.');
|
||||
|
||||
await page.click(`[data-testid="edit-${NEW_COLUMN_TEST_CASE.name}"]`);
|
||||
await page.waitForSelector('#tableTestForm_params_minLength');
|
||||
const minLengthValue = await page
|
||||
.locator('#tableTestForm_params_minLength')
|
||||
.inputValue();
|
||||
|
||||
expect(minLengthValue).toBe('4');
|
||||
|
||||
await page.locator('button').getByText('Cancel').click();
|
||||
});
|
||||
|
||||
await test.step('Delete', async () => {
|
||||
await deleteTestCase(page, NEW_COLUMN_TEST_CASE.name);
|
||||
});
|
||||
});
|
||||
|
||||
test('Profiler matrix and test case graph should visible', async ({ page }) => {
|
||||
const DATA_QUALITY_TABLE = {
|
||||
term: 'dim_address',
|
||||
serviceName: 'sample_data',
|
||||
testCaseName: 'column_value_max_to_be_between',
|
||||
};
|
||||
|
||||
await visitEntityPage({
|
||||
page,
|
||||
searchTerm: DATA_QUALITY_TABLE.term,
|
||||
dataTestId: `${DATA_QUALITY_TABLE.serviceName}-${DATA_QUALITY_TABLE.term}`,
|
||||
});
|
||||
await page.waitForSelector(`[data-testid="entity-header-display-name"]`);
|
||||
|
||||
await expect(
|
||||
page.locator(`[data-testid="entity-header-display-name"]`)
|
||||
).toContainText(DATA_QUALITY_TABLE.term);
|
||||
|
||||
const profilerResponse = page.waitForResponse(
|
||||
`/api/v1/tables/*/tableProfile/latest`
|
||||
);
|
||||
await page.click('[data-testid="profiler"]');
|
||||
await profilerResponse;
|
||||
await page.waitForTimeout(1000);
|
||||
await page
|
||||
.getByRole('menuitem', {
|
||||
name: 'Column Profile',
|
||||
})
|
||||
.click();
|
||||
const getProfilerInfo = page.waitForResponse(
|
||||
'/api/v1/tables/*/columnProfile?*'
|
||||
);
|
||||
await page.locator('[data-row-key="shop_id"]').getByText('shop_id').click();
|
||||
await getProfilerInfo;
|
||||
|
||||
await expect(page.locator('#count_graph')).toBeVisible();
|
||||
await expect(page.locator('#proportion_graph')).toBeVisible();
|
||||
await expect(page.locator('#math_graph')).toBeVisible();
|
||||
await expect(page.locator('#sum_graph')).toBeVisible();
|
||||
|
||||
await page
|
||||
.getByRole('menuitem', {
|
||||
name: 'Data Quality',
|
||||
})
|
||||
.click();
|
||||
|
||||
await page.waitForSelector(
|
||||
`[data-testid="${DATA_QUALITY_TABLE.testCaseName}"]`
|
||||
);
|
||||
const getTestCaseDetails = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/name/*?fields=*'
|
||||
);
|
||||
const getTestResult = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/*/testCaseResult?*'
|
||||
);
|
||||
await page
|
||||
.locator(`[data-testid="${DATA_QUALITY_TABLE.testCaseName}"]`)
|
||||
.getByText(DATA_QUALITY_TABLE.testCaseName)
|
||||
.click();
|
||||
|
||||
await getTestCaseDetails;
|
||||
await getTestResult;
|
||||
|
||||
await expect(
|
||||
page.locator(`#${DATA_QUALITY_TABLE.testCaseName}_graph`)
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('TestCase with Array params value', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
const testCase = table2.testCasesResponseData[0];
|
||||
const testCaseName = testCase?.['name'];
|
||||
await visitDataQualityTab(page, table2);
|
||||
|
||||
await test.step(
|
||||
'Array params value should be visible while editing the test case',
|
||||
async () => {
|
||||
await expect(
|
||||
page.locator(`[data-testid="${testCaseName}"]`)
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.locator(`[data-testid="edit-${testCaseName}"]`)
|
||||
).toBeVisible();
|
||||
|
||||
await page.click(`[data-testid="edit-${testCaseName}"]`);
|
||||
|
||||
await expect(
|
||||
page.locator('#tableTestForm_params_allowedValues_0_value')
|
||||
).toHaveValue('gmail');
|
||||
await expect(
|
||||
page.locator('#tableTestForm_params_allowedValues_1_value')
|
||||
).toHaveValue('yahoo');
|
||||
await expect(
|
||||
page.locator('#tableTestForm_params_allowedValues_2_value')
|
||||
).toHaveValue('collate');
|
||||
}
|
||||
);
|
||||
|
||||
await test.step('Validate patch request for edit test case', async () => {
|
||||
await page.fill(
|
||||
'#tableTestForm_displayName',
|
||||
'Table test case display name'
|
||||
);
|
||||
|
||||
await expect(page.locator('#tableTestForm_table')).toHaveValue(
|
||||
table2.entityResponseData?.['name']
|
||||
);
|
||||
await expect(page.locator('#tableTestForm_column')).toHaveValue('email');
|
||||
await expect(page.locator('#tableTestForm_name')).toHaveValue(testCaseName);
|
||||
await expect(page.locator('#tableTestForm_testDefinition')).toHaveValue(
|
||||
'Column Values To Be In Set'
|
||||
);
|
||||
|
||||
// Edit test case display name
|
||||
const updateTestCaseResponse = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/api/v1/dataQuality/testCases/') &&
|
||||
response.request().method() === 'PATCH'
|
||||
);
|
||||
await page.click('.ant-modal-footer >> text=Submit');
|
||||
const updateResponse1 = await updateTestCaseResponse;
|
||||
const body1 = await updateResponse1.request().postData();
|
||||
|
||||
expect(body1).toEqual(
|
||||
JSON.stringify([
|
||||
{
|
||||
op: 'add',
|
||||
path: '/displayName',
|
||||
value: 'Table test case display name',
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
// Edit test case description
|
||||
await page.click(`[data-testid="edit-${testCaseName}"]`);
|
||||
await page.fill(descriptionBox, 'Test case description');
|
||||
const updateTestCaseResponse2 = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/api/v1/dataQuality/testCases/') &&
|
||||
response.request().method() === 'PATCH'
|
||||
);
|
||||
await page.click('.ant-modal-footer >> text=Submit');
|
||||
const updateResponse2 = await updateTestCaseResponse2;
|
||||
const body2 = await updateResponse2.request().postData();
|
||||
|
||||
expect(body2).toEqual(
|
||||
JSON.stringify([
|
||||
{ op: 'add', path: '/description', value: 'Test case description' },
|
||||
])
|
||||
);
|
||||
|
||||
// Edit test case parameter values
|
||||
await page.click(`[data-testid="edit-${testCaseName}"]`);
|
||||
await page.fill('#tableTestForm_params_allowedValues_0_value', 'test');
|
||||
const updateTestCaseResponse3 = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/api/v1/dataQuality/testCases/') &&
|
||||
response.request().method() === 'PATCH'
|
||||
);
|
||||
await page.click('.ant-modal-footer >> text=Submit');
|
||||
const updateResponse3 = await updateTestCaseResponse3;
|
||||
const body3 = await updateResponse3.request().postData();
|
||||
|
||||
expect(body3).toEqual(
|
||||
JSON.stringify([
|
||||
{
|
||||
op: 'replace',
|
||||
path: '/parameterValues/0/value',
|
||||
value: '["test","yahoo","collate"]',
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Update test case display name from Data Quality page',
|
||||
async () => {
|
||||
const getTestCase = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*'
|
||||
);
|
||||
await sidebarClick(page, SidebarItem.DATA_QUALITY);
|
||||
await page.click('[data-testid="by-test-cases"]');
|
||||
await getTestCase;
|
||||
const searchTestCaseResponse = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*q=*${testCaseName}*`
|
||||
);
|
||||
await page.fill(
|
||||
'[data-testid="test-case-container"] [data-testid="searchbar"]',
|
||||
testCaseName
|
||||
);
|
||||
await searchTestCaseResponse;
|
||||
await page.waitForSelector('.ant-spin', {
|
||||
state: 'detached',
|
||||
});
|
||||
await page.click(`[data-testid="edit-${testCaseName}"]`);
|
||||
await page.waitForSelector('.ant-modal-title');
|
||||
|
||||
await expect(page.locator('#tableTestForm_displayName')).toHaveValue(
|
||||
'Table test case display name'
|
||||
);
|
||||
|
||||
await page.locator('#tableTestForm_displayName').clear();
|
||||
await page.fill('#tableTestForm_displayName', 'Updated display name');
|
||||
await page.click('.ant-modal-footer >> text=Submit');
|
||||
await toastNotification(page, 'Test case updated successfully.');
|
||||
|
||||
await expect(page.locator(`[data-testid="${testCaseName}"]`)).toHaveText(
|
||||
'Updated display name'
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('Update profiler setting modal', async ({ page }) => {
|
||||
const profilerSetting = {
|
||||
profileSample: '60',
|
||||
sampleDataCount: '100',
|
||||
profileQuery: 'select * from table',
|
||||
excludeColumns: 'user_id',
|
||||
includeColumns: 'shop_id',
|
||||
partitionColumnName: 'name',
|
||||
partitionIntervalType: 'COLUMN-VALUE',
|
||||
partitionValues: 'test',
|
||||
};
|
||||
|
||||
await table1.visitEntityPage(page);
|
||||
await page.getByTestId('profiler').click();
|
||||
await page
|
||||
.getByTestId('profiler-tab-left-panel')
|
||||
.getByText('Table Profile')
|
||||
.click();
|
||||
|
||||
await page.click('[data-testid="profiler-setting-btn"]');
|
||||
await page.waitForSelector('.ant-modal-body');
|
||||
await page.locator('[data-testid="slider-input"]').clear();
|
||||
await page
|
||||
.locator('[data-testid="slider-input"]')
|
||||
.fill(profilerSetting.profileSample);
|
||||
|
||||
await page.locator('[data-testid="sample-data-count-input"]').clear();
|
||||
await page
|
||||
.locator('[data-testid="sample-data-count-input"]')
|
||||
.fill(profilerSetting.sampleDataCount);
|
||||
await page.locator('[data-testid="exclude-column-select"]').click();
|
||||
await page.keyboard.type(`${profilerSetting.excludeColumns}`);
|
||||
await page.keyboard.press('Enter');
|
||||
await page.locator('.CodeMirror-scroll').click();
|
||||
await page.keyboard.type(profilerSetting.profileQuery);
|
||||
|
||||
await page.locator('[data-testid="include-column-select"]').click();
|
||||
await page
|
||||
.locator('.ant-select-dropdown')
|
||||
.locator(
|
||||
`[title="${profilerSetting.includeColumns}"]:not(.ant-select-dropdown-hidden)`
|
||||
)
|
||||
.last()
|
||||
.click();
|
||||
await page.locator('[data-testid="enable-partition-switch"]').click();
|
||||
await page.locator('[data-testid="interval-type"]').click();
|
||||
await page
|
||||
.locator('.ant-select-dropdown')
|
||||
.locator(
|
||||
`[title="${profilerSetting.partitionIntervalType}"]:not(.ant-select-dropdown-hidden)`
|
||||
)
|
||||
.click();
|
||||
|
||||
await page.locator('#includeColumnsProfiler_partitionColumnName').click();
|
||||
await page
|
||||
.locator('.ant-select-dropdown')
|
||||
.locator(
|
||||
`[title="${profilerSetting.partitionColumnName}"]:not(.ant-select-dropdown-hidden)`
|
||||
)
|
||||
.last()
|
||||
.click();
|
||||
await page
|
||||
.locator('[data-testid="partition-value"]')
|
||||
.fill(profilerSetting.partitionValues);
|
||||
|
||||
const updateTableProfilerConfigResponse = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/api/v1/tables/') &&
|
||||
response.url().includes('/tableProfilerConfig') &&
|
||||
response.request().method() === 'PUT'
|
||||
);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
const updateResponse = await updateTableProfilerConfigResponse;
|
||||
const requestBody = await updateResponse.request().postData();
|
||||
|
||||
expect(requestBody).toEqual(
|
||||
JSON.stringify({
|
||||
excludeColumns: ['user_id'],
|
||||
profileQuery: 'select * from table',
|
||||
profileSample: 60,
|
||||
profileSampleType: 'PERCENTAGE',
|
||||
includeColumns: [{ columnName: 'shop_id' }],
|
||||
partitioning: {
|
||||
partitionColumnName: 'name',
|
||||
partitionIntervalType: 'COLUMN-VALUE',
|
||||
partitionValues: ['test'],
|
||||
enablePartitioning: true,
|
||||
},
|
||||
sampleDataCount: 100,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('TestCase filters', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
const { apiContext, afterAction } = await getApiContext(page);
|
||||
const filterTable1 = new TableClass();
|
||||
|
||||
await filterTable1.create(apiContext);
|
||||
const filterTable2 = {
|
||||
...filterTable1.entity,
|
||||
name: `${filterTable1.entity.name}-model`,
|
||||
};
|
||||
const filterTable2Response = await apiContext
|
||||
.post('/api/v1/tables', {
|
||||
data: filterTable2,
|
||||
})
|
||||
.then((response) => response.json());
|
||||
const domain = new Domain();
|
||||
await domain.create(apiContext);
|
||||
|
||||
// Add domain to table
|
||||
await filterTable1.visitEntityPage(page);
|
||||
await assignDomain(page, domain.responseData);
|
||||
const testCases = [
|
||||
`pw_first_table_column_count_to_be_between_${uuid()}`,
|
||||
`pw_second_table_column_count_to_be_between_${uuid()}`,
|
||||
`pw_third_table_column_count_to_be_between_${uuid()}`,
|
||||
];
|
||||
const smilerNameTestCase = testCases.map((test) => `${test}_version_2`);
|
||||
await filterTable1.patch({
|
||||
apiContext,
|
||||
patchData: [
|
||||
{
|
||||
op: 'add',
|
||||
path: '/tags/0',
|
||||
value: {
|
||||
tagFQN: 'PII.None',
|
||||
name: 'None',
|
||||
description: 'Non PII',
|
||||
source: 'Classification',
|
||||
labelType: 'Manual',
|
||||
state: 'Confirmed',
|
||||
},
|
||||
},
|
||||
{
|
||||
op: 'add',
|
||||
path: '/tags/1',
|
||||
value: {
|
||||
tagFQN: 'Tier.Tier2',
|
||||
name: 'Tier2',
|
||||
source: 'Classification',
|
||||
labelType: 'Manual',
|
||||
state: 'Confirmed',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await filterTable1.createTestSuiteAndPipelines(apiContext);
|
||||
const { testSuiteData: testSuite2Response } =
|
||||
await filterTable1.createTestSuiteAndPipelines(apiContext, {
|
||||
executableEntityReference: filterTable2Response?.['fullyQualifiedName'],
|
||||
});
|
||||
|
||||
const testCaseResult = {
|
||||
result: 'Found min=10001, max=27809 vs. the expected min=90001, max=96162.',
|
||||
testCaseStatus: 'Failed',
|
||||
testResultValue: [
|
||||
{
|
||||
name: 'minValueForMaxInCol',
|
||||
value: '10001',
|
||||
},
|
||||
{
|
||||
name: 'maxValueForMaxInCol',
|
||||
value: '27809',
|
||||
},
|
||||
],
|
||||
timestamp: getCurrentMillis(),
|
||||
};
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const testCase1 = await filterTable1.createTestCase(apiContext, {
|
||||
name: testCases[i],
|
||||
});
|
||||
await filterTable1.addTestCaseResult(
|
||||
apiContext,
|
||||
testCase1?.['fullyQualifiedName'],
|
||||
testCaseResult
|
||||
);
|
||||
const testCase2 = await filterTable1.createTestCase(apiContext, {
|
||||
name: smilerNameTestCase[i],
|
||||
entityLink: `<#E::table::${filterTable2Response?.['fullyQualifiedName']}>`,
|
||||
testSuite: testSuite2Response?.['fullyQualifiedName'],
|
||||
});
|
||||
await filterTable1.addTestCaseResult(
|
||||
apiContext,
|
||||
testCase2?.['fullyQualifiedName'],
|
||||
testCaseResult
|
||||
);
|
||||
}
|
||||
|
||||
const verifyFilterTestCase = async (page: Page) => {
|
||||
for (const testCase of testCases) {
|
||||
const element = page.locator(`[data-testid="${testCase}"]`);
|
||||
|
||||
await expect(element).toBeVisible();
|
||||
}
|
||||
};
|
||||
|
||||
const verifyFilter2TestCase = async (page: Page, negation = false) => {
|
||||
for (const testCase of smilerNameTestCase) {
|
||||
const element = page.locator(`[data-testid="${testCase}"]`);
|
||||
if (negation) {
|
||||
await expect(element).not.toBeVisible();
|
||||
} else {
|
||||
await expect(element).toBeVisible();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await sidebarClick(page, SidebarItem.DATA_QUALITY);
|
||||
const getTestCaseListData = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*'
|
||||
);
|
||||
await page.click('[data-testid="by-test-cases"]');
|
||||
await getTestCaseListData;
|
||||
// get all the filters
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
await page.click('[value="tableFqn"]');
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
await page.click('[value="testPlatforms"]');
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
await page.click('[value="lastRunRange"]');
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
await page.click('[value="serviceName"]');
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
await page.click('[value="tags"]');
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
await page.click('[value="tier"]');
|
||||
|
||||
// Test case search filter
|
||||
const searchTestCaseResponse = page.waitForResponse(
|
||||
(url) =>
|
||||
url.url().includes('/api/v1/dataQuality/testCases/search/list') &&
|
||||
url.url().includes(testCases[0])
|
||||
);
|
||||
await page.fill(
|
||||
'[data-testid="test-case-container"] [data-testid="searchbar"]',
|
||||
testCases[0]
|
||||
);
|
||||
await searchTestCaseResponse;
|
||||
|
||||
await expect(page.locator(`[data-testid="${testCases[0]}"]`)).toBeVisible();
|
||||
|
||||
// clear the search filter
|
||||
const getTestCaseResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*'
|
||||
);
|
||||
await page.locator('.ant-input-clear-icon').click();
|
||||
await getTestCaseResponse;
|
||||
|
||||
// Test case filter by service name
|
||||
const serviceResponse = page.waitForResponse(
|
||||
'/api/v1/search/query?q=*index=database_service_search_index*'
|
||||
);
|
||||
await page.fill('#serviceName', filterTable1.service.name);
|
||||
await serviceResponse;
|
||||
|
||||
const testCaseByServiceName = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*serviceName=${filterTable1.service.name}*`
|
||||
);
|
||||
await page
|
||||
.locator('.ant-select-dropdown')
|
||||
.filter({
|
||||
hasNot: page.locator('.ant-select-dropdown-hidden'),
|
||||
has: page.locator(`[data-testid="${filterTable1.service.name}"]`),
|
||||
})
|
||||
.click();
|
||||
await testCaseByServiceName;
|
||||
await verifyFilterTestCase(page);
|
||||
await verifyFilter2TestCase(page);
|
||||
|
||||
// remove service filter
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
const getTestCase = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*'
|
||||
);
|
||||
await page.click('[value="serviceName"]');
|
||||
await getTestCase;
|
||||
|
||||
// Test case filter by Tags
|
||||
const tagResponse = page.waitForResponse(
|
||||
'/api/v1/search/query?q=*index=tag_search_index*'
|
||||
);
|
||||
await page
|
||||
.getByTestId('tags-select-filter')
|
||||
.locator('div')
|
||||
.filter({ hasText: 'Tags' })
|
||||
.click();
|
||||
await page.fill('#tags', 'PII.None');
|
||||
await tagResponse;
|
||||
|
||||
const getTestCaseByTag = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*tags=PII.None*'
|
||||
);
|
||||
await page
|
||||
.locator('.ant-select-dropdown')
|
||||
.filter({
|
||||
hasNot: page.locator('.ant-select-dropdown-hidden'),
|
||||
has: page.locator(`[data-testid="PII.None"]`),
|
||||
})
|
||||
.click();
|
||||
await getTestCaseByTag;
|
||||
await verifyFilterTestCase(page);
|
||||
await verifyFilter2TestCase(page, true);
|
||||
|
||||
// remove tags filter
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
const getTestCaseWithoutTag = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*'
|
||||
);
|
||||
await page.click('[value="tags"]');
|
||||
await getTestCaseWithoutTag;
|
||||
|
||||
// Test case filter by Tier
|
||||
|
||||
await page.click('#tier');
|
||||
const getTestCaseByTier = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*tier=Tier.Tier2*'
|
||||
);
|
||||
await page.getByTestId('Tier.Tier2').getByText('Tier.Tier2').click();
|
||||
await getTestCaseByTier;
|
||||
await verifyFilterTestCase(page);
|
||||
await verifyFilter2TestCase(page, true);
|
||||
|
||||
// remove tier filter
|
||||
await page.click('[data-testid="advanced-filter"]');
|
||||
const getTestCaseWithoutTier = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*'
|
||||
);
|
||||
await page.click('[value="tier"]');
|
||||
await getTestCaseWithoutTier;
|
||||
|
||||
// Test case filter by table name
|
||||
const tableSearchResponse = page.waitForResponse(
|
||||
`/api/v1/search/query?q=*index=table_search_index*`
|
||||
);
|
||||
await page.fill('#tableFqn', filterTable1.entity.name);
|
||||
await tableSearchResponse;
|
||||
const getTestCaseByTable = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*entityLink=*${filterTable1.entity.name}*`
|
||||
);
|
||||
|
||||
await page
|
||||
.getByTestId(filterTable1.entityResponseData?.['fullyQualifiedName'])
|
||||
.click();
|
||||
await getTestCaseByTable;
|
||||
await verifyFilterTestCase(page);
|
||||
await verifyFilter2TestCase(page, true);
|
||||
|
||||
// Test case filter by test type column
|
||||
const testCaseTypeByColumn = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*testCaseType=column*`
|
||||
);
|
||||
await page.getByTestId('test-case-type-select-filter').click();
|
||||
await page.getByTitle('Column').click();
|
||||
await testCaseTypeByColumn;
|
||||
|
||||
await expect(
|
||||
page.locator('[data-testid="search-error-placeholder"]')
|
||||
).toBeVisible();
|
||||
|
||||
// Test case filter by test type table
|
||||
const testCaseTypeByTable = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*testCaseType=table*`
|
||||
);
|
||||
await page.getByTestId('test-case-type-select-filter').click();
|
||||
await page
|
||||
.locator('.ant-select-dropdown')
|
||||
.filter({
|
||||
hasNot: page.locator('.ant-select-dropdown-hidden'),
|
||||
has: page.locator(`[title="Table"]`),
|
||||
hasText: 'Table',
|
||||
})
|
||||
.click();
|
||||
await testCaseTypeByTable;
|
||||
await verifyFilterTestCase(page);
|
||||
|
||||
// Test case filter by test type all
|
||||
const testCaseTypeByAll = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*testCaseType=all*`
|
||||
);
|
||||
await page.getByTestId('test-case-type-select-filter').click();
|
||||
await page.getByTitle('All').nth(1).click();
|
||||
await testCaseTypeByAll;
|
||||
|
||||
// Test case filter by status
|
||||
const testCaseStatusBySuccess = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*testCaseStatus=Success*`
|
||||
);
|
||||
await page.getByTestId('status-select-filter').click();
|
||||
await page.getByTitle('Success').click();
|
||||
await testCaseStatusBySuccess;
|
||||
|
||||
await expect(
|
||||
page.locator('[data-testid="search-error-placeholder"]')
|
||||
).toBeVisible();
|
||||
|
||||
// Test case filter by status
|
||||
const testCaseStatusByFailed = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*testCaseStatus=Failed*`
|
||||
);
|
||||
await page.getByTestId('status-select-filter').click();
|
||||
await page.getByTitle('Failed').click();
|
||||
await testCaseStatusByFailed;
|
||||
await verifyFilterTestCase(page);
|
||||
await verifyFilter2TestCase(page, true);
|
||||
|
||||
// Test case filter by platform
|
||||
const testCasePlatformByDBT = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*testPlatforms=DBT*`
|
||||
);
|
||||
await page.getByTestId('platform-select-filter').click();
|
||||
await page.getByTitle('DBT').click();
|
||||
await testCasePlatformByDBT;
|
||||
|
||||
await expect(
|
||||
page.locator('[data-testid="search-error-placeholder"]')
|
||||
).toBeVisible();
|
||||
|
||||
const getTestCaseWithoutPlatform = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*'
|
||||
);
|
||||
await page
|
||||
.getByTestId('platform-select-filter')
|
||||
.locator('.ant-select-clear')
|
||||
.click();
|
||||
await getTestCaseWithoutPlatform;
|
||||
const testCasePlatformByOpenMetadata = page.waitForResponse(
|
||||
`/api/v1/dataQuality/testCases/search/list?*testPlatforms=OpenMetadata*`
|
||||
);
|
||||
await page.getByTestId('platform-select-filter').click();
|
||||
await page.getByTitle('OpenMetadata').click();
|
||||
await testCasePlatformByOpenMetadata;
|
||||
await clickOutside(page);
|
||||
await verifyFilterTestCase(page);
|
||||
await verifyFilter2TestCase(page, true);
|
||||
const url = page.url();
|
||||
await page.reload();
|
||||
|
||||
await expect(page.url()).toBe(url);
|
||||
|
||||
await page.getByTestId('advanced-filter').click();
|
||||
await page.click('[value="testPlatforms"]');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await expect(page.getByTestId('platform-select-filter')).not.toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
|
||||
await expect(page.locator('[value="tier"]')).not.toBeVisible();
|
||||
|
||||
// Apply domain globally
|
||||
await page.locator('[data-testid="domain-dropdown"]').click();
|
||||
await page
|
||||
.locator(`li[data-menu-id*='${domain.responseData?.['name']}']`)
|
||||
.click();
|
||||
await sidebarClick(page, SidebarItem.DATA_QUALITY);
|
||||
const getTestCaseList = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/search/list?*'
|
||||
);
|
||||
await page.click('[data-testid="by-test-cases"]');
|
||||
await getTestCaseList;
|
||||
await verifyFilterTestCase(page);
|
||||
await verifyFilter2TestCase(page, true);
|
||||
} finally {
|
||||
await filterTable1.delete(apiContext);
|
||||
await domain.delete(apiContext);
|
||||
await afterAction();
|
||||
}
|
||||
});
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import test, { expect } from '@playwright/test';
|
||||
import { POSTGRES, REDSHIFT } from '../../constant/service';
|
||||
import { MYSQL, POSTGRES, REDSHIFT } from '../../constant/service';
|
||||
import { GlobalSettingOptions } from '../../constant/settings';
|
||||
import AirflowIngestionClass from '../../support/entity/ingestion/AirflowIngestionClass';
|
||||
import BigQueryIngestionClass from '../../support/entity/ingestion/BigQueryIngestionClass';
|
||||
@ -83,7 +83,9 @@ services.forEach((ServiceClass) => {
|
||||
});
|
||||
|
||||
if (
|
||||
[POSTGRES.serviceType, REDSHIFT.serviceType].includes(service.serviceType)
|
||||
[POSTGRES.serviceType, REDSHIFT.serviceType, MYSQL].includes(
|
||||
service.serviceType
|
||||
)
|
||||
) {
|
||||
test(`Service specific tests`, async ({ page }) => {
|
||||
await service.runAdditionalTests(page, test);
|
||||
|
@ -12,8 +12,12 @@
|
||||
*/
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { TableClass } from '../../support/entity/TableClass';
|
||||
import { getApiContext, redirectToHomePage } from '../../utils/common';
|
||||
import { deleteTestCase } from '../../utils/testCases';
|
||||
import {
|
||||
descriptionBox,
|
||||
getApiContext,
|
||||
redirectToHomePage,
|
||||
} from '../../utils/common';
|
||||
import { deleteTestCase, visitDataQualityTab } from '../../utils/testCases';
|
||||
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
@ -231,3 +235,104 @@ test('Custom SQL Query', async ({ page }) => {
|
||||
await afterAction();
|
||||
}
|
||||
});
|
||||
|
||||
test('Column Values To Be Not Null', async ({ page }) => {
|
||||
test.slow();
|
||||
|
||||
const NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE = {
|
||||
name: 'id_column_values_to_be_not_null',
|
||||
displayName: 'ID Column Values To Be Not Null',
|
||||
column: 'user_id',
|
||||
type: 'columnValuesToBeNotNull',
|
||||
label: 'Column Values To Be Not Null',
|
||||
description: 'New table test case for columnValuesToBeNotNull',
|
||||
};
|
||||
await redirectToHomePage(page);
|
||||
const { afterAction, apiContext } = await getApiContext(page);
|
||||
const table = new TableClass();
|
||||
await table.create(apiContext);
|
||||
|
||||
await visitDataQualityTab(page, table);
|
||||
await page.click('[data-testid="profiler-add-table-test-btn"]');
|
||||
await page.click('[data-testid="column"]');
|
||||
|
||||
try {
|
||||
await test.step('Create', async () => {
|
||||
const testDefinitionResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testDefinitions?limit=*&entityType=COLUMN&testPlatform=OpenMetadata&supportedDataType=NUMERIC'
|
||||
);
|
||||
await page.click('#tableTestForm_column');
|
||||
await page.click(
|
||||
`[title="${NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.column}"]`
|
||||
);
|
||||
await testDefinitionResponse;
|
||||
await page.fill(
|
||||
'#tableTestForm_testName',
|
||||
NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.name
|
||||
);
|
||||
|
||||
await page.fill(
|
||||
'#tableTestForm_testTypeId',
|
||||
NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.type
|
||||
);
|
||||
await page.click(
|
||||
`[title="${NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.label}"]`
|
||||
);
|
||||
await page.fill(
|
||||
descriptionBox,
|
||||
NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.description
|
||||
);
|
||||
|
||||
await page.click('[data-testid="submit-test"]');
|
||||
await page.waitForSelector('[data-testid="success-line"]');
|
||||
await page.waitForSelector('[data-testid="view-service-button"]');
|
||||
const testCaseResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases?fields=*'
|
||||
);
|
||||
await page.click(`[data-testid="view-service-button"]`);
|
||||
await testCaseResponse;
|
||||
await page.click('[data-testid="profiler-tab-left-panel"]');
|
||||
|
||||
await expect(
|
||||
page.locator(
|
||||
`[data-testid="${NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.name}"]`
|
||||
)
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Edit', async () => {
|
||||
await page
|
||||
.getByTestId(`edit-${NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.name}`)
|
||||
.click();
|
||||
|
||||
await expect(page.locator('.ant-modal-title')).toHaveText(
|
||||
`Edit ${NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.name}`
|
||||
);
|
||||
await expect(page.locator('#tableTestForm_name')).toHaveValue(
|
||||
NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.name
|
||||
);
|
||||
|
||||
await page.locator('#tableTestForm_displayName').clear();
|
||||
await page.fill(
|
||||
'#tableTestForm_displayName',
|
||||
NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.displayName
|
||||
);
|
||||
await page.getByText('New table test case for').first().click();
|
||||
await page.keyboard.type(' update');
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
await expect(page.getByRole('alert')).toContainText(
|
||||
'Test case updated successfully.'
|
||||
);
|
||||
|
||||
await page.getByTestId('content-wrapper').getByLabel('close').click();
|
||||
});
|
||||
|
||||
await test.step('Delete', async () => {
|
||||
await deleteTestCase(page, NEW_COLUMN_TEST_CASE_WITH_NULL_TYPE.name);
|
||||
});
|
||||
} finally {
|
||||
await table.delete(apiContext);
|
||||
await afterAction();
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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 { expect, test } from '@playwright/test';
|
||||
import { SidebarItem } from '../../constant/sidebar';
|
||||
import { EntityTypeEndpoint } from '../../support/entity/Entity.interface';
|
||||
import { TableClass } from '../../support/entity/TableClass';
|
||||
import { UserClass } from '../../support/user/UserClass';
|
||||
import {
|
||||
createNewPage,
|
||||
descriptionBox,
|
||||
redirectToHomePage,
|
||||
toastNotification,
|
||||
uuid,
|
||||
} from '../../utils/common';
|
||||
import { addMultiOwner, removeOwnersFromList } from '../../utils/entity';
|
||||
import { sidebarClick } from '../../utils/sidebar';
|
||||
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
const table = new TableClass();
|
||||
const user1 = new UserClass();
|
||||
const user2 = new UserClass();
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
await table.create(apiContext);
|
||||
await user1.create(apiContext);
|
||||
await user2.create(apiContext);
|
||||
await table.createTestCase(apiContext);
|
||||
await table.createTestCase(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.afterAll(async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
await table.delete(apiContext);
|
||||
await user1.delete(apiContext);
|
||||
await user2.delete(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await redirectToHomePage(page);
|
||||
});
|
||||
|
||||
test('Logical TestSuite', async ({ page }) => {
|
||||
const NEW_TEST_SUITE = {
|
||||
name: `mysql_matrix-${uuid()}`,
|
||||
description: 'mysql critical matrix',
|
||||
};
|
||||
const testCaseName1 = table.testCasesResponseData?.[0]?.['name'];
|
||||
const testCaseName2 = table.testCasesResponseData?.[1]?.['name'];
|
||||
await sidebarClick(page, SidebarItem.DATA_QUALITY);
|
||||
const testSuite = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testSuites/search/list?*testSuiteType=logical*'
|
||||
);
|
||||
await page.click('[data-testid="by-test-suites"]');
|
||||
await testSuite;
|
||||
|
||||
await test.step('Create', async () => {
|
||||
await page.click('[data-testid="add-test-suite-btn"]');
|
||||
await page.fill('[data-testid="test-suite-name"]', NEW_TEST_SUITE.name);
|
||||
await page.fill(descriptionBox, NEW_TEST_SUITE.description);
|
||||
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
const getTestCase = page.waitForResponse(
|
||||
'/api/v1/search/query?q=*&index=test_case_search_index*'
|
||||
);
|
||||
await page.fill('[data-testid="searchbar"]', testCaseName1);
|
||||
await getTestCase;
|
||||
|
||||
await page.click(`[data-testid="${testCaseName1}"]`);
|
||||
await page.click('[data-testid="submit"]');
|
||||
|
||||
await page.waitForSelector('[data-testid="success-line"]', {
|
||||
state: 'visible',
|
||||
});
|
||||
|
||||
await expect(page.locator('[data-testid="success-line"]')).toContainText(
|
||||
'has been created successfully'
|
||||
);
|
||||
|
||||
const testSuiteResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testSuites/name/*'
|
||||
);
|
||||
await page.click(`[data-testid="view-service-button"]`);
|
||||
await testSuiteResponse;
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'User as Owner assign, update & delete for test suite',
|
||||
async () => {
|
||||
await addMultiOwner({
|
||||
page,
|
||||
ownerNames: [user1.getUserName()],
|
||||
activatorBtnDataTestId: 'edit-owner',
|
||||
endpoint: EntityTypeEndpoint.TestSuites,
|
||||
type: 'Users',
|
||||
});
|
||||
await removeOwnersFromList({
|
||||
page,
|
||||
ownerNames: [user1.getUserName()],
|
||||
endpoint: EntityTypeEndpoint.TestSuites,
|
||||
});
|
||||
await addMultiOwner({
|
||||
page,
|
||||
ownerNames: [user2.getUserName()],
|
||||
activatorBtnDataTestId: 'edit-owner',
|
||||
endpoint: EntityTypeEndpoint.TestSuites,
|
||||
type: 'Users',
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
await test.step('Add test case to logical test suite', async () => {
|
||||
const testCaseResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases?fields=*'
|
||||
);
|
||||
await page.click('[data-testid="add-test-case-btn"]');
|
||||
await testCaseResponse;
|
||||
|
||||
const getTestCase = page.waitForResponse(
|
||||
'/api/v1/search/query?q=*&index=test_case_search_index*'
|
||||
);
|
||||
await page.fill('[data-testid="searchbar"]', testCaseName2);
|
||||
await getTestCase;
|
||||
|
||||
await page.click(`[data-testid="${testCaseName2}"]`);
|
||||
const updateTestCase = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/logicalTestCases'
|
||||
);
|
||||
await page.click('[data-testid="submit"]');
|
||||
await updateTestCase;
|
||||
await page.waitForSelector('.ant-modal-content', {
|
||||
state: 'detached',
|
||||
});
|
||||
});
|
||||
|
||||
await test.step('Remove test case from logical test suite', async () => {
|
||||
await page.click(`[data-testid="remove-${testCaseName1}"]`);
|
||||
const removeTestCase1 = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/logicalTestCases/*/*'
|
||||
);
|
||||
await page.click('[data-testid="save-button"]');
|
||||
await removeTestCase1;
|
||||
await page.click(`[data-testid="remove-${testCaseName2}"]`);
|
||||
const removeTestCase2 = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/logicalTestCases/*/*'
|
||||
);
|
||||
await page.click('[data-testid="save-button"]');
|
||||
await removeTestCase2;
|
||||
});
|
||||
|
||||
await test.step('Test suite filters', async () => {
|
||||
const owner = user2.getUserName();
|
||||
const testSuite = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testSuites/search/list?*testSuiteType=logical*'
|
||||
);
|
||||
await page.getByRole('link', { name: 'Test Suites' }).click();
|
||||
await testSuite;
|
||||
|
||||
await page.click('[data-testid="owner-select-filter"]');
|
||||
await page.waitForSelector("[data-testid='select-owner-tabs']", {
|
||||
state: 'visible',
|
||||
});
|
||||
const getOwnerList = page.waitForResponse('/api/v1/users?*isBot=false*');
|
||||
await page.click('.ant-tabs [id*=tab-users]');
|
||||
await getOwnerList;
|
||||
await page.waitForSelector(`[data-testid="loader"]`, {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
const searchOwner = page.waitForResponse(
|
||||
'api/v1/search/query?q=*&index=user_search_index*'
|
||||
);
|
||||
await page.fill('[data-testid="owner-select-users-search-bar"]', owner);
|
||||
await searchOwner;
|
||||
|
||||
const testSuiteByOwner = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testSuites/search/list?*owner=*'
|
||||
);
|
||||
await page.click(`.ant-popover [title="${owner}"]`);
|
||||
await testSuiteByOwner;
|
||||
await page.waitForSelector(`[data-testid="${NEW_TEST_SUITE.name}"]`, {
|
||||
state: 'visible',
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.locator(`[data-testid="${NEW_TEST_SUITE.name}"]`)
|
||||
).toBeVisible();
|
||||
|
||||
await page.click(`[data-testid="${NEW_TEST_SUITE.name}"]`);
|
||||
});
|
||||
|
||||
await test.step('Delete', async () => {
|
||||
await page.click('[data-testid="manage-button"]');
|
||||
await page.click('[data-testid="delete-button"]');
|
||||
|
||||
// Click on Permanent/Hard delete option
|
||||
await page.click('[data-testid="hard-delete-option"]');
|
||||
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||||
const deleteResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testSuites/*?hardDelete=true&recursive=true'
|
||||
);
|
||||
await page.click('[data-testid="confirm-button"]');
|
||||
await deleteResponse;
|
||||
await toastNotification(
|
||||
page,
|
||||
`"${NEW_TEST_SUITE.name}" deleted successfully!`
|
||||
);
|
||||
});
|
||||
});
|
@ -38,6 +38,7 @@ export enum EntityTypeEndpoint {
|
||||
API_COLLECTION = 'apiCollections',
|
||||
API_ENDPOINT = 'apiEndpoints',
|
||||
DATA_PRODUCT = 'dataProducts',
|
||||
TestSuites = 'dataQuality/testSuites',
|
||||
}
|
||||
|
||||
export type EntityDataType = {
|
||||
@ -67,6 +68,15 @@ export enum ENTITY_PATH {
|
||||
}
|
||||
|
||||
export type TestCaseData = {
|
||||
parameterValues: unknown[];
|
||||
testDefinition: string;
|
||||
parameterValues?: unknown[];
|
||||
name?: string;
|
||||
entityLink?: string;
|
||||
testDefinition?: string;
|
||||
testSuite?: string;
|
||||
};
|
||||
|
||||
export type TestSuiteData = {
|
||||
name?: string;
|
||||
executableEntityReference?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
@ -11,10 +11,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { APIRequestContext, Page } from '@playwright/test';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { SERVICE_TYPE } from '../../constant/service';
|
||||
import { uuid } from '../../utils/common';
|
||||
import { visitEntityPage } from '../../utils/entity';
|
||||
import { EntityTypeEndpoint, TestCaseData } from './Entity.interface';
|
||||
import {
|
||||
EntityTypeEndpoint,
|
||||
TestCaseData,
|
||||
TestSuiteData,
|
||||
} from './Entity.interface';
|
||||
import { EntityClass } from './EntityClass';
|
||||
|
||||
export class TableClass extends EntityClass {
|
||||
@ -167,7 +172,10 @@ export class TableClass extends EntityClass {
|
||||
});
|
||||
}
|
||||
|
||||
async createTestSuiteAndPipelines(apiContext: APIRequestContext) {
|
||||
async createTestSuiteAndPipelines(
|
||||
apiContext: APIRequestContext,
|
||||
testSuite?: TestSuiteData
|
||||
) {
|
||||
if (!this.entityResponseData) {
|
||||
await this.create(apiContext);
|
||||
}
|
||||
@ -179,6 +187,7 @@ export class TableClass extends EntityClass {
|
||||
executableEntityReference:
|
||||
this.entityResponseData?.['fullyQualifiedName'],
|
||||
description: 'Playwright test suite for table',
|
||||
...testSuite,
|
||||
},
|
||||
})
|
||||
.then((res) => res.json());
|
||||
@ -240,13 +249,13 @@ export class TableClass extends EntityClass {
|
||||
data: {
|
||||
name: `pw-test-case-${uuid()}`,
|
||||
entityLink: `<#E::table::${this.entityResponseData?.['fullyQualifiedName']}>`,
|
||||
testDefinition:
|
||||
testCaseData?.testDefinition ?? 'tableRowCountToBeBetween',
|
||||
testDefinition: 'tableRowCountToBeBetween',
|
||||
testSuite: this.testSuiteResponseData?.['fullyQualifiedName'],
|
||||
parameterValues: testCaseData?.parameterValues ?? [
|
||||
parameterValues: [
|
||||
{ name: 'minValue', value: 12 },
|
||||
{ name: 'maxValue', value: 34 },
|
||||
],
|
||||
...testCaseData,
|
||||
},
|
||||
})
|
||||
.then((res) => res.json());
|
||||
@ -256,6 +265,43 @@ export class TableClass extends EntityClass {
|
||||
return testCase;
|
||||
}
|
||||
|
||||
async addTestCaseResult(
|
||||
apiContext: APIRequestContext,
|
||||
testCaseFqn: string,
|
||||
testCaseResult: unknown
|
||||
) {
|
||||
const testCaseResultResponse = await apiContext.put(
|
||||
`/api/v1/dataQuality/testCases/${testCaseFqn}/testCaseResult`,
|
||||
{ data: testCaseResult }
|
||||
);
|
||||
|
||||
return await testCaseResultResponse.json();
|
||||
}
|
||||
|
||||
async patch({
|
||||
apiContext,
|
||||
patchData,
|
||||
}: {
|
||||
apiContext: APIRequestContext;
|
||||
patchData: Operation[];
|
||||
}) {
|
||||
const response = await apiContext.patch(
|
||||
`/api/v1/tables/name/${this.entityResponseData?.['fullyQualifiedName']}`,
|
||||
{
|
||||
data: patchData,
|
||||
headers: {
|
||||
'Content-Type': 'application/json-patch+json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.entityResponseData = await response.json();
|
||||
|
||||
return {
|
||||
entity: this.entityResponseData,
|
||||
};
|
||||
}
|
||||
|
||||
async delete(apiContext: APIRequestContext) {
|
||||
const serviceResponse = await apiContext.delete(
|
||||
`/api/v1/services/databaseServices/name/${encodeURIComponent(
|
||||
|
@ -10,9 +10,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { expect, Page } from '@playwright/test';
|
||||
import {
|
||||
expect,
|
||||
Page,
|
||||
PlaywrightTestArgs,
|
||||
PlaywrightWorkerArgs,
|
||||
TestType,
|
||||
} from '@playwright/test';
|
||||
import { env } from 'process';
|
||||
import { uuid } from '../../../utils/common';
|
||||
import {
|
||||
getApiContext,
|
||||
redirectToHomePage,
|
||||
toastNotification,
|
||||
uuid,
|
||||
} from '../../../utils/common';
|
||||
import { visitEntityPage } from '../../../utils/entity';
|
||||
import { visitServiceDetailsPage } from '../../../utils/service';
|
||||
import {
|
||||
checkServiceFieldSectionHighlighting,
|
||||
Services,
|
||||
@ -22,6 +35,7 @@ import ServiceBaseClass from './ServiceBaseClass';
|
||||
class MysqlIngestionClass extends ServiceBaseClass {
|
||||
name: string;
|
||||
tableFilter: string[];
|
||||
profilerTable = 'alert_entity';
|
||||
constructor() {
|
||||
super(
|
||||
Services.Database,
|
||||
@ -62,6 +76,84 @@ class MysqlIngestionClass extends ServiceBaseClass {
|
||||
}
|
||||
}
|
||||
|
||||
async runAdditionalTests(
|
||||
page: Page,
|
||||
test: TestType<PlaywrightTestArgs, PlaywrightWorkerArgs>
|
||||
) {
|
||||
await test.step('Add Profiler ingestion', async () => {
|
||||
const { apiContext } = await getApiContext(page);
|
||||
await redirectToHomePage(page);
|
||||
await visitServiceDetailsPage(
|
||||
page,
|
||||
{
|
||||
type: this.category,
|
||||
name: this.serviceName,
|
||||
displayName: this.serviceName,
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
await page.click('[data-testid="ingestions"]');
|
||||
await page.waitForSelector('[data-testid="ingestion-details-container"]');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.click('[data-testid="add-new-ingestion-button"]');
|
||||
await page.waitForTimeout(1000);
|
||||
await page.click('[data-menu-id*="profiler"]');
|
||||
|
||||
await page.waitForSelector('#root\\/profileSample');
|
||||
await page.fill('#root\\/profileSample', '10');
|
||||
await page.click('[data-testid="submit-btn"]');
|
||||
// Make sure we create ingestion with None schedule to avoid conflict between Airflow and Argo behavior
|
||||
await this.scheduleIngestion(page);
|
||||
|
||||
await page.click('[data-testid="view-service-button"]');
|
||||
|
||||
// Header available once page loads
|
||||
await page.waitForSelector('[data-testid="data-assets-header"]');
|
||||
await page.getByTestId('loader').waitFor({ state: 'detached' });
|
||||
await page.getByTestId('ingestions').click();
|
||||
await page
|
||||
.getByLabel('Ingestions')
|
||||
.getByTestId('loader')
|
||||
.waitFor({ state: 'detached' });
|
||||
|
||||
const response = await apiContext
|
||||
.get(
|
||||
`/api/v1/services/ingestionPipelines?service=${encodeURIComponent(
|
||||
this.serviceName
|
||||
)}&pipelineType=profiler&serviceType=databaseService&limit=1`
|
||||
)
|
||||
.then((res) => res.json());
|
||||
|
||||
await page.click(
|
||||
`[data-row-key*="${response.data[0].name}"] [data-testid="more-actions"]`
|
||||
);
|
||||
await page.getByTestId('run-button').click();
|
||||
|
||||
await toastNotification(page, `Pipeline triggered successfully!`);
|
||||
|
||||
await this.handleIngestionRetry('profiler', page);
|
||||
});
|
||||
|
||||
await test.step('Validate profiler ingestion', async () => {
|
||||
await visitEntityPage({
|
||||
page,
|
||||
searchTerm: this.profilerTable,
|
||||
dataTestId: `${this.serviceName}-${this.profilerTable}`,
|
||||
});
|
||||
});
|
||||
|
||||
await page.getByTestId('profiler').click();
|
||||
await page
|
||||
.getByTestId('profiler-tab-left-panel')
|
||||
.getByText('Table Profile')
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.locator('[data-testid="no-profiler-placeholder"]')
|
||||
).not.toBeVisible();
|
||||
}
|
||||
|
||||
async validateIngestionDetails(page: Page) {
|
||||
await page.waitForSelector('.ant-select-selection-item-content');
|
||||
|
||||
|
@ -176,10 +176,10 @@ class ServiceBaseClass {
|
||||
.getByLabel('Ingestions')
|
||||
.getByTestId('loader')
|
||||
.waitFor({ state: 'detached' });
|
||||
|
||||
|
||||
// need manual wait to settle down the deployed pipeline, before triggering the pipeline
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
|
||||
await page.getByTestId('more-actions').first().click();
|
||||
await page.getByTestId('run-button').click();
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { expect, Page } from '@playwright/test';
|
||||
import { TableClass } from '../support/entity/TableClass';
|
||||
|
||||
export const deleteTestCase = async (page: Page, testCaseName: string) => {
|
||||
await page.getByTestId(`delete-${testCaseName}`).click();
|
||||
@ -18,7 +19,24 @@ export const deleteTestCase = async (page: Page, testCaseName: string) => {
|
||||
|
||||
await expect(page.getByTestId('confirm-button')).toBeEnabled();
|
||||
|
||||
const deleteResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases/*?hardDelete=true&recursive=true'
|
||||
);
|
||||
await page.getByTestId('confirm-button').click();
|
||||
await deleteResponse;
|
||||
|
||||
await expect(page.getByRole('alert')).toHaveText(/deleted successfully!/);
|
||||
};
|
||||
|
||||
export const visitDataQualityTab = async (page: Page, table: TableClass) => {
|
||||
await table.visitEntityPage(page);
|
||||
await page.getByTestId('profiler').click();
|
||||
const testCaseResponse = page.waitForResponse(
|
||||
'/api/v1/dataQuality/testCases?fields=*'
|
||||
);
|
||||
await page
|
||||
.getByTestId('profiler-tab-left-panel')
|
||||
.getByText('Data Quality')
|
||||
.click();
|
||||
await testCaseResponse;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user