mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-19 12:50:20 +00:00
Migrate: tags spec to playwright (#17758)
This commit is contained in:
parent
1c90eaaf3d
commit
ae9de3057d
@ -1,379 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 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 {
|
|
||||||
addNewTagToEntity,
|
|
||||||
descriptionBox,
|
|
||||||
interceptURL,
|
|
||||||
verifyResponseStatusCode,
|
|
||||||
} from '../../common/common';
|
|
||||||
import {
|
|
||||||
deleteClassification,
|
|
||||||
submitForm,
|
|
||||||
validateForm,
|
|
||||||
visitClassificationPage,
|
|
||||||
} from '../../common/TagUtils';
|
|
||||||
import { visitEntityDetailsPage } from '../../common/Utils/Entity';
|
|
||||||
import { assignTags, removeTags } from '../../common/Utils/Tags';
|
|
||||||
import {
|
|
||||||
DELETE_TERM,
|
|
||||||
NEW_CLASSIFICATION,
|
|
||||||
NEW_TAG,
|
|
||||||
SEARCH_ENTITY_TABLE,
|
|
||||||
} from '../../constants/constants';
|
|
||||||
import { EntityType } from '../../constants/Entity.interface';
|
|
||||||
|
|
||||||
const permanentDeleteModal = (entity) => {
|
|
||||||
cy.get('[data-testid="delete-confirmation-modal"]')
|
|
||||||
.should('exist')
|
|
||||||
.then(() => {
|
|
||||||
cy.get('[role="dialog"]').should('be.visible');
|
|
||||||
cy.get('[data-testid="modal-header"]').should('be.visible');
|
|
||||||
});
|
|
||||||
cy.get('[data-testid="modal-header"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.should('contain', `Delete ${entity}`);
|
|
||||||
cy.get('[data-testid="confirmation-text-input"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.type(DELETE_TERM);
|
|
||||||
|
|
||||||
cy.get('[data-testid="confirm-button"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.should('not.disabled')
|
|
||||||
.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Classification Page', { tags: 'Governance' }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.login();
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
`/api/v1/tags?fields=usageCount&parent=${NEW_CLASSIFICATION.name}&limit=10`,
|
|
||||||
'getTagList'
|
|
||||||
);
|
|
||||||
interceptURL('GET', `/api/v1/permissions/classification/*`, 'permissions');
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
`/api/v1/search/query?q=*%20AND%20disabled:false&index=tag_search_index*`,
|
|
||||||
'suggestTag'
|
|
||||||
);
|
|
||||||
visitClassificationPage();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should render basic elements on page', () => {
|
|
||||||
cy.get('[data-testid="add-classification"]').should('be.visible');
|
|
||||||
cy.get('[data-testid="add-new-tag-button"]').should('be.visible');
|
|
||||||
cy.get('[data-testid="manage-button"]').should('be.visible');
|
|
||||||
cy.get('[data-testid="description-container"]').should('be.visible');
|
|
||||||
cy.get('[data-testid="table"]').should('be.visible');
|
|
||||||
|
|
||||||
cy.get('.ant-table-thead > tr > .ant-table-cell')
|
|
||||||
.eq(0)
|
|
||||||
.contains('Tag')
|
|
||||||
.should('be.visible');
|
|
||||||
cy.get('.ant-table-thead > tr > .ant-table-cell')
|
|
||||||
.eq(1)
|
|
||||||
.contains('Display Name')
|
|
||||||
.should('be.visible');
|
|
||||||
cy.get('.ant-table-thead > tr > .ant-table-cell')
|
|
||||||
.eq(2)
|
|
||||||
.contains('Description')
|
|
||||||
.should('be.visible');
|
|
||||||
cy.get('.ant-table-thead > tr > .ant-table-cell')
|
|
||||||
.eq(3)
|
|
||||||
.contains('Actions')
|
|
||||||
.should('be.visible');
|
|
||||||
|
|
||||||
cy.get('.activeCategory > .tag-category')
|
|
||||||
.should('be.visible')
|
|
||||||
.invoke('text')
|
|
||||||
.then((text) => {
|
|
||||||
cy.get('.activeCategory > .tag-category')
|
|
||||||
.should('be.visible')
|
|
||||||
.invoke('text')
|
|
||||||
.then((heading) => {
|
|
||||||
expect(text).to.equal(heading);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Create classification with validation checks', () => {
|
|
||||||
interceptURL('POST', 'api/v1/classifications', 'createTagCategory');
|
|
||||||
cy.get('[data-testid="add-classification"]').should('be.visible').click();
|
|
||||||
cy.get('[data-testid="modal-container"]')
|
|
||||||
.should('exist')
|
|
||||||
.then(() => {
|
|
||||||
cy.get('[role="dialog"]').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
// validation should work
|
|
||||||
validateForm();
|
|
||||||
|
|
||||||
cy.get('[data-testid="name"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.clear()
|
|
||||||
.type(NEW_CLASSIFICATION.name);
|
|
||||||
cy.get('[data-testid="displayName"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.type(NEW_CLASSIFICATION.displayName);
|
|
||||||
cy.get(descriptionBox)
|
|
||||||
.should('be.visible')
|
|
||||||
.type(NEW_CLASSIFICATION.description);
|
|
||||||
cy.get('[data-testid="mutually-exclusive-button"]')
|
|
||||||
.scrollIntoView()
|
|
||||||
.should('be.visible')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
submitForm();
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@createTagCategory', 201);
|
|
||||||
cy.get('[data-testid="modal-container"]').should('not.exist');
|
|
||||||
cy.get('[data-testid="data-summary-container"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.and('contain', NEW_CLASSIFICATION.displayName);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Create tag with validation checks', () => {
|
|
||||||
cy.get('[data-testid="data-summary-container"]')
|
|
||||||
.contains(NEW_CLASSIFICATION.displayName)
|
|
||||||
.should('be.visible')
|
|
||||||
.as('newCategory');
|
|
||||||
|
|
||||||
cy.get('@newCategory')
|
|
||||||
.click()
|
|
||||||
.parent()
|
|
||||||
.should('have.class', 'activeCategory');
|
|
||||||
cy.get('[data-testid="add-new-tag-button"]').should('be.visible').click();
|
|
||||||
cy.get('[data-testid="modal-container"]')
|
|
||||||
.should('exist')
|
|
||||||
.then(() => {
|
|
||||||
cy.get('[role="dialog"]').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
// validation should work
|
|
||||||
validateForm();
|
|
||||||
|
|
||||||
cy.get('[data-testid="name"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.clear()
|
|
||||||
.type(NEW_TAG.name);
|
|
||||||
cy.get('[data-testid="displayName"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.type(NEW_TAG.displayName);
|
|
||||||
cy.get(descriptionBox).should('be.visible').type(NEW_TAG.description);
|
|
||||||
|
|
||||||
cy.get('[data-testid="icon-url"]').scrollIntoView().type(NEW_TAG.icon);
|
|
||||||
cy.get('[data-testid="tags_color-color-input"]')
|
|
||||||
.scrollIntoView()
|
|
||||||
.type(NEW_TAG.color);
|
|
||||||
|
|
||||||
interceptURL('POST', '/api/v1/tags', 'createTag');
|
|
||||||
submitForm();
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@createTag', 201);
|
|
||||||
|
|
||||||
cy.get('[data-testid="table"]').should('contain', NEW_TAG.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`Assign tag to table ${SEARCH_ENTITY_TABLE.table_3.displayName}`, () => {
|
|
||||||
const entity = SEARCH_ENTITY_TABLE.table_3;
|
|
||||||
visitEntityDetailsPage({
|
|
||||||
term: entity.term,
|
|
||||||
serviceName: entity.serviceName,
|
|
||||||
entity: entity.entity,
|
|
||||||
});
|
|
||||||
addNewTagToEntity(NEW_TAG);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Assign tag to DatabaseSchema', () => {
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
'/api/v1/permissions/databaseSchema/name/*',
|
|
||||||
'permissions'
|
|
||||||
);
|
|
||||||
interceptURL('PUT', '/api/v1/feed/tasks/*/resolve', 'taskResolve');
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
'/api/v1/databaseSchemas/name/*',
|
|
||||||
'databaseSchemasPage'
|
|
||||||
);
|
|
||||||
interceptURL('PATCH', '/api/v1/databaseSchemas/*', 'addTags');
|
|
||||||
|
|
||||||
const entity = SEARCH_ENTITY_TABLE.table_3;
|
|
||||||
const tag = 'PII.Sensitive';
|
|
||||||
|
|
||||||
visitEntityDetailsPage({
|
|
||||||
term: entity.term,
|
|
||||||
serviceName: entity.serviceName,
|
|
||||||
entity: entity.entity,
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('[data-testid="breadcrumb-link"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.contains(entity.schemaName)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@databaseSchemasPage', 200);
|
|
||||||
verifyResponseStatusCode('@permissions', 200);
|
|
||||||
|
|
||||||
assignTags(tag, EntityType.DatabaseSchema);
|
|
||||||
|
|
||||||
removeTags(tag, EntityType.DatabaseSchema);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Assign tag using Task & Suggestion flow to DatabaseSchema', () => {
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
'/api/v1/permissions/databaseSchema/name/*',
|
|
||||||
'permissions'
|
|
||||||
);
|
|
||||||
interceptURL('PUT', '/api/v1/feed/tasks/*/resolve', 'taskResolve');
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
'/api/v1/databaseSchemas/name/*',
|
|
||||||
'databaseSchemasPage'
|
|
||||||
);
|
|
||||||
|
|
||||||
const entity = SEARCH_ENTITY_TABLE.table_2;
|
|
||||||
const tag = 'Personal';
|
|
||||||
const assignee = 'admin';
|
|
||||||
|
|
||||||
visitEntityDetailsPage({
|
|
||||||
term: entity.term,
|
|
||||||
serviceName: entity.serviceName,
|
|
||||||
entity: entity.entity,
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('[data-testid="breadcrumb-link"]')
|
|
||||||
.should('be.visible')
|
|
||||||
.contains(entity.schemaName)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@databaseSchemasPage', 200);
|
|
||||||
verifyResponseStatusCode('@permissions', 200);
|
|
||||||
|
|
||||||
// Create task to add tags
|
|
||||||
interceptURL('POST', '/api/v1/feed', 'taskCreated');
|
|
||||||
cy.get('[data-testid="request-entity-tags"]').should('exist').click();
|
|
||||||
|
|
||||||
// set assignees for task
|
|
||||||
cy.get(
|
|
||||||
'[data-testid="select-assignee"] > .ant-select-selector > .ant-select-selection-overflow'
|
|
||||||
)
|
|
||||||
.click()
|
|
||||||
.type(assignee);
|
|
||||||
cy.get(`[data-testid="${assignee}"]`).scrollIntoView().click();
|
|
||||||
|
|
||||||
// click outside the select box
|
|
||||||
cy.clickOutside();
|
|
||||||
|
|
||||||
cy.get('[data-testid="tag-selector"]').click().type(tag);
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@suggestTag', 200);
|
|
||||||
cy.get('[data-testid="tag-PersonalData.Personal"]').click();
|
|
||||||
|
|
||||||
cy.get('[data-testid="tags-label"]').click();
|
|
||||||
|
|
||||||
cy.get('[data-testid="submit-tag-request"]').click();
|
|
||||||
verifyResponseStatusCode('@taskCreated', 201);
|
|
||||||
|
|
||||||
// Accept the tag suggestion which is created
|
|
||||||
cy.get('.ant-btn-compact-first-item').contains('Accept Suggestion').click();
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@taskResolve', 200);
|
|
||||||
verifyResponseStatusCode('@databaseSchemasPage', 200);
|
|
||||||
cy.get('[data-testid="table"]').click();
|
|
||||||
|
|
||||||
cy.reload();
|
|
||||||
verifyResponseStatusCode('@databaseSchemasPage', 200);
|
|
||||||
|
|
||||||
cy.get('[data-testid="tags-container"]').scrollIntoView().contains(tag);
|
|
||||||
|
|
||||||
cy.get('[data-testid="edit-button"]').click();
|
|
||||||
|
|
||||||
// Remove all added tags
|
|
||||||
cy.get('[data-testid="remove-tags"]').click({ multiple: true });
|
|
||||||
|
|
||||||
interceptURL('PATCH', '/api/v1/databaseSchemas/*', 'removeTags');
|
|
||||||
cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click();
|
|
||||||
verifyResponseStatusCode('@removeTags', 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should have correct tag usage count and redirection should work', () => {
|
|
||||||
cy.get('[data-testid="data-summary-container"]')
|
|
||||||
.contains(NEW_CLASSIFICATION.displayName)
|
|
||||||
.should('be.visible')
|
|
||||||
.as('newCategory');
|
|
||||||
|
|
||||||
cy.get('@newCategory')
|
|
||||||
.click()
|
|
||||||
.parent()
|
|
||||||
.should('have.class', 'activeCategory');
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@permissions', 200);
|
|
||||||
cy.get('[data-testid="entity-header-display-name"]')
|
|
||||||
.invoke('text')
|
|
||||||
.then((text) => {
|
|
||||||
// Get the text of the first menu item
|
|
||||||
if (text !== NEW_CLASSIFICATION.displayName) {
|
|
||||||
verifyResponseStatusCode('@getTags', 200);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('[data-testid="usage-count"]').should('be.visible').as('count');
|
|
||||||
cy.get('@count')
|
|
||||||
.invoke('text')
|
|
||||||
.then((text) => {
|
|
||||||
expect(text).to.equal('1');
|
|
||||||
});
|
|
||||||
|
|
||||||
interceptURL(
|
|
||||||
'GET',
|
|
||||||
'api/v1/search/query?q=&index=**',
|
|
||||||
'getEntityDetailsPage'
|
|
||||||
);
|
|
||||||
cy.get('@count').click();
|
|
||||||
verifyResponseStatusCode('@getEntityDetailsPage', 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Remove tag', () => {
|
|
||||||
interceptURL(
|
|
||||||
'DELETE',
|
|
||||||
'/api/v1/tags/*?recursive=true&hardDelete=true',
|
|
||||||
'deleteTag'
|
|
||||||
);
|
|
||||||
cy.get('[data-testid="data-summary-container"]')
|
|
||||||
.contains(NEW_CLASSIFICATION.displayName)
|
|
||||||
.click()
|
|
||||||
.parent()
|
|
||||||
.should('have.class', 'activeCategory');
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@permissions', 200);
|
|
||||||
|
|
||||||
cy.get('[data-testid="table"]').should('contain', NEW_TAG.name);
|
|
||||||
|
|
||||||
cy.get('[data-testid="table"]').find('[data-testid="delete-tag"]').click();
|
|
||||||
cy.wait(500); // adding manual wait to open modal, as it depends on click not an api.
|
|
||||||
permanentDeleteModal(NEW_TAG.name);
|
|
||||||
|
|
||||||
verifyResponseStatusCode('@deleteTag', 200);
|
|
||||||
cy.wait(500);
|
|
||||||
cy.get('[data-testid="table"]')
|
|
||||||
.contains(NEW_TAG.name)
|
|
||||||
.should('not.be.exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Remove classification', () => {
|
|
||||||
deleteClassification(NEW_CLASSIFICATION);
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,382 @@
|
|||||||
|
/*
|
||||||
|
* 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 { SidebarItem } from '../../constant/sidebar';
|
||||||
|
import { TableClass } from '../../support/entity/TableClass';
|
||||||
|
import {
|
||||||
|
clickOutside,
|
||||||
|
createNewPage,
|
||||||
|
descriptionBox,
|
||||||
|
redirectToHomePage,
|
||||||
|
uuid,
|
||||||
|
} from '../../utils/common';
|
||||||
|
import { sidebarClick } from '../../utils/sidebar';
|
||||||
|
import { submitForm, validateForm } from '../../utils/tag';
|
||||||
|
|
||||||
|
const NEW_CLASSIFICATION = {
|
||||||
|
name: `PlaywrightClassification-${uuid()}`,
|
||||||
|
displayName: `PlaywrightClassification-${uuid()}`,
|
||||||
|
description: 'This is the PlaywrightClassification',
|
||||||
|
};
|
||||||
|
const NEW_TAG = {
|
||||||
|
name: `PlaywrightTag-${uuid()}`,
|
||||||
|
displayName: `PlaywrightTag-${uuid()}`,
|
||||||
|
renamedName: `PlaywrightTag-${uuid()}`,
|
||||||
|
description: 'This is the PlaywrightTag',
|
||||||
|
color: '#FF5733',
|
||||||
|
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAF8AAACFCAMAAAAKN9SOAAAAA1BMVEXmGSCqexgYAAAAI0lEQVRoge3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAHgaMeAAAUWJHZ4AAAAASUVORK5CYII=',
|
||||||
|
};
|
||||||
|
const tagFqn = `${NEW_CLASSIFICATION.name}.${NEW_TAG.name}`;
|
||||||
|
|
||||||
|
const permanentDeleteModal = async (page: Page, entity: string) => {
|
||||||
|
await page.waitForSelector('.ant-modal-content', {
|
||||||
|
state: 'visible',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.locator('.ant-modal-content')).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.locator('[data-testid="modal-header"]')).toContainText(
|
||||||
|
`Delete ${entity}`
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||||||
|
await page.click('[data-testid="confirm-button"]');
|
||||||
|
};
|
||||||
|
|
||||||
|
test.describe.configure({ mode: 'serial' });
|
||||||
|
|
||||||
|
// use the admin user to login
|
||||||
|
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||||
|
|
||||||
|
const table = new TableClass();
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await createNewPage(browser);
|
||||||
|
await table.create(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await createNewPage(browser);
|
||||||
|
await table.delete(apiContext);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Classification Page', async ({ page }) => {
|
||||||
|
await test.step('Should render basic elements on page', async () => {
|
||||||
|
const getTags = page.waitForResponse('/api/v1/tags*');
|
||||||
|
await sidebarClick(page, SidebarItem.TAGS);
|
||||||
|
await getTags;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="add-classification"]')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="add-new-tag-button"]')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.locator('[data-testid="manage-button"]')).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="description-container"]')
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.locator('[data-testid="table"]')).toBeVisible();
|
||||||
|
|
||||||
|
const headers = await page
|
||||||
|
.locator('.ant-table-thead > tr > .ant-table-cell')
|
||||||
|
.allTextContents();
|
||||||
|
|
||||||
|
expect(headers).toEqual(['Tag', 'Display Name', 'Description', 'Actions']);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Create classification with validation checks', async () => {
|
||||||
|
await page.click('[data-testid="add-classification"]');
|
||||||
|
await page.waitForSelector('.ant-modal-content', {
|
||||||
|
state: 'visible',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.locator('.ant-modal-content')).toBeVisible();
|
||||||
|
|
||||||
|
await validateForm(page);
|
||||||
|
|
||||||
|
await page.fill('[data-testid="name"]', NEW_CLASSIFICATION.name);
|
||||||
|
await page.fill(
|
||||||
|
'[data-testid="displayName"]',
|
||||||
|
NEW_CLASSIFICATION.displayName
|
||||||
|
);
|
||||||
|
await page.fill(descriptionBox, NEW_CLASSIFICATION.description);
|
||||||
|
await page.click('[data-testid="mutually-exclusive-button"]');
|
||||||
|
|
||||||
|
const createTagCategoryResponse = page.waitForResponse(
|
||||||
|
'api/v1/classifications'
|
||||||
|
);
|
||||||
|
await submitForm(page);
|
||||||
|
await createTagCategoryResponse;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="modal-container"]')
|
||||||
|
).not.toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="data-summary-container"]')
|
||||||
|
).toContainText(NEW_CLASSIFICATION.displayName);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Create tag with validation checks', async () => {
|
||||||
|
await page.click(`text=${NEW_CLASSIFICATION.displayName}`);
|
||||||
|
|
||||||
|
await expect(page.locator('.activeCategory')).toContainText(
|
||||||
|
NEW_CLASSIFICATION.displayName
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.click('[data-testid="add-new-tag-button"]');
|
||||||
|
|
||||||
|
await page.waitForSelector('.ant-modal-content', {
|
||||||
|
state: 'visible',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.locator('.ant-modal-content')).toBeVisible();
|
||||||
|
|
||||||
|
await validateForm(page);
|
||||||
|
|
||||||
|
await page.fill('[data-testid="name"]', NEW_TAG.name);
|
||||||
|
await page.fill('[data-testid="displayName"]', NEW_TAG.displayName);
|
||||||
|
await page.fill(descriptionBox, NEW_TAG.description);
|
||||||
|
await page.fill('[data-testid="icon-url"]', NEW_TAG.icon);
|
||||||
|
await page.fill('[data-testid="tags_color-color-input"]', NEW_TAG.color);
|
||||||
|
|
||||||
|
const createTagResponse = page.waitForResponse('api/v1/tags');
|
||||||
|
await submitForm(page);
|
||||||
|
await createTagResponse;
|
||||||
|
|
||||||
|
await expect(page.locator('[data-testid="table"]')).toContainText(
|
||||||
|
NEW_TAG.name
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step(`Assign tag to table`, async () => {
|
||||||
|
await table.visitEntityPage(page);
|
||||||
|
const { name, displayName } = NEW_TAG;
|
||||||
|
|
||||||
|
await page.click(
|
||||||
|
'[data-testid="classification-tags-0"] [data-testid="entity-tags"] [data-testid="add-tag"]'
|
||||||
|
);
|
||||||
|
await page.fill('[data-testid="tag-selector"] input', name);
|
||||||
|
await page.click(`[data-testid="tag-${tagFqn}"]`);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="tag-selector"] > .ant-select-selector')
|
||||||
|
).toContainText(displayName);
|
||||||
|
|
||||||
|
const saveAssociatedTag = page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.request().method() === 'PATCH' &&
|
||||||
|
response
|
||||||
|
.url()
|
||||||
|
.includes(`/api/v1/tables/${table.entityResponseData?.['id']}`)
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="saveAssociatedTag"]');
|
||||||
|
await saveAssociatedTag;
|
||||||
|
|
||||||
|
await page.waitForSelector('.ant-select-dropdown', {
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole('row', { name: 'user_id numeric Unique' })
|
||||||
|
.getByTestId('tags-container')
|
||||||
|
).toContainText(displayName);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator(
|
||||||
|
'[data-testid="classification-tags-0"] [data-testid="tags-container"] [data-testid="icon"]'
|
||||||
|
)
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step(
|
||||||
|
'Assign tag using Task & Suggestion flow to DatabaseSchema',
|
||||||
|
async () => {
|
||||||
|
const entity = table.schema;
|
||||||
|
const tag = 'Personal';
|
||||||
|
const assignee = 'admin';
|
||||||
|
|
||||||
|
const databaseSchemaPage = page.waitForResponse(
|
||||||
|
'api/v1/databaseSchemas/name/*'
|
||||||
|
);
|
||||||
|
const permissions = page.waitForResponse(
|
||||||
|
'api/v1/permissions/databaseSchema/name/*'
|
||||||
|
);
|
||||||
|
await page.click(
|
||||||
|
`[data-testid="breadcrumb-link"]:has-text("${entity.name}")`
|
||||||
|
);
|
||||||
|
|
||||||
|
await databaseSchemaPage;
|
||||||
|
await permissions;
|
||||||
|
|
||||||
|
await page.click('[data-testid="request-entity-tags"]');
|
||||||
|
|
||||||
|
await page.click('[data-testid="select-assignee"]');
|
||||||
|
const assigneeResponse = page.waitForResponse(
|
||||||
|
'/api/v1/search/suggest?q=*&index=user_search_index*team_search_index*'
|
||||||
|
);
|
||||||
|
await page.keyboard.type(assignee);
|
||||||
|
await page.click(`[data-testid="${assignee}"]`);
|
||||||
|
await assigneeResponse;
|
||||||
|
|
||||||
|
await clickOutside(page);
|
||||||
|
|
||||||
|
const suggestTag = page.waitForResponse(
|
||||||
|
'api/v1/search/query?q=*%20AND%20disabled:false&index=tag_search_index*'
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="tag-selector"]');
|
||||||
|
await page.keyboard.type(tag);
|
||||||
|
await suggestTag;
|
||||||
|
await page.click('[data-testid="tag-PersonalData.Personal"]');
|
||||||
|
|
||||||
|
await page.click('[data-testid="tags-label"]');
|
||||||
|
const taskCreated = page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.request().method() === 'POST' &&
|
||||||
|
response.url().includes('api/v1/feed')
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="submit-tag-request"]');
|
||||||
|
await taskCreated;
|
||||||
|
|
||||||
|
const acceptSuggestion = page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.request().method() === 'PUT' &&
|
||||||
|
response.url().includes('/api/v1/feed/tasks/') &&
|
||||||
|
response.url().includes('/resolve')
|
||||||
|
);
|
||||||
|
await page.click(
|
||||||
|
'.ant-btn-compact-first-item:has-text("Accept Suggestion")'
|
||||||
|
);
|
||||||
|
await acceptSuggestion;
|
||||||
|
await page.click('[data-testid="table"]');
|
||||||
|
|
||||||
|
const databaseSchemasPage = page.waitForResponse(
|
||||||
|
'api/v1/databaseSchemas/name/*'
|
||||||
|
);
|
||||||
|
await page.reload();
|
||||||
|
await databaseSchemasPage;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="tags-container"]')
|
||||||
|
).toContainText(tag);
|
||||||
|
|
||||||
|
await page.click('[data-testid="edit-button"]');
|
||||||
|
|
||||||
|
await page.click('[data-testid="remove-tags"]');
|
||||||
|
|
||||||
|
const removeTags = page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.request().method() === 'PATCH' &&
|
||||||
|
response.url().includes('/api/v1/databaseSchemas/')
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="saveAssociatedTag"]');
|
||||||
|
await removeTags;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await test.step(
|
||||||
|
'Should have correct tag usage count and redirection should work',
|
||||||
|
async () => {
|
||||||
|
const getTags = page.waitForResponse('/api/v1/tags*');
|
||||||
|
await sidebarClick(page, SidebarItem.TAGS);
|
||||||
|
await getTags;
|
||||||
|
await page
|
||||||
|
.locator(`[data-testid="side-panel-classification"]`)
|
||||||
|
.filter({ hasText: NEW_CLASSIFICATION.displayName })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(page.locator('.activeCategory')).toContainText(
|
||||||
|
NEW_CLASSIFICATION.displayName
|
||||||
|
);
|
||||||
|
|
||||||
|
const count = await page
|
||||||
|
.locator('[data-testid="usage-count"]')
|
||||||
|
.textContent();
|
||||||
|
|
||||||
|
expect(count).toBe('1');
|
||||||
|
|
||||||
|
const getEntityDetailsPage = page.waitForResponse(
|
||||||
|
'api/v1/search/query?q=&index=**'
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="usage-count"]');
|
||||||
|
await getEntityDetailsPage;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await test.step('Delete tag', async () => {
|
||||||
|
const getTags = page.waitForResponse('/api/v1/tags*');
|
||||||
|
await sidebarClick(page, SidebarItem.TAGS);
|
||||||
|
await getTags;
|
||||||
|
await page
|
||||||
|
.locator(`[data-testid="side-panel-classification"]`)
|
||||||
|
.filter({ hasText: NEW_CLASSIFICATION.displayName })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(page.locator('.activeCategory')).toContainText(
|
||||||
|
NEW_CLASSIFICATION.displayName
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(page.locator('[data-testid="table"]')).toContainText(
|
||||||
|
NEW_TAG.name
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.click('[data-testid="table"] [data-testid="delete-tag"]');
|
||||||
|
await page.waitForTimeout(500); // adding manual wait to open modal, as it depends on click not an api.
|
||||||
|
const deleteTag = page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.request().method() === 'DELETE' &&
|
||||||
|
response.url().includes('/api/v1/tags/')
|
||||||
|
);
|
||||||
|
await permanentDeleteModal(page, NEW_TAG.name);
|
||||||
|
await deleteTag;
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
await expect(page.locator('[data-testid="table"]')).not.toContainText(
|
||||||
|
NEW_TAG.name
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Remove classification', async () => {
|
||||||
|
await expect(page.getByTestId('entity-header-display-name')).toContainText(
|
||||||
|
NEW_CLASSIFICATION.displayName
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.click('[data-testid="manage-button"]');
|
||||||
|
|
||||||
|
await page.click('[data-testid="delete-button"]');
|
||||||
|
|
||||||
|
await page.click('[data-testid="hard-delete-option"]');
|
||||||
|
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||||||
|
|
||||||
|
const deleteClassification = page.waitForResponse(
|
||||||
|
(response) =>
|
||||||
|
response.request().method() === 'DELETE' &&
|
||||||
|
response.url().includes('/api/v1/classifications/')
|
||||||
|
);
|
||||||
|
await page.click('[data-testid="confirm-button"]');
|
||||||
|
await deleteClassification;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator('[data-testid="data-summary-container"]')
|
||||||
|
.filter({ hasText: NEW_CLASSIFICATION.name })
|
||||||
|
).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
@ -10,11 +10,21 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Page } from '@playwright/test';
|
import { expect, Page } from '@playwright/test';
|
||||||
import { SidebarItem } from '../constant/sidebar';
|
import { SidebarItem } from '../constant/sidebar';
|
||||||
import { redirectToHomePage } from './common';
|
import {
|
||||||
|
NAME_MIN_MAX_LENGTH_VALIDATION_ERROR,
|
||||||
|
NAME_VALIDATION_ERROR,
|
||||||
|
redirectToHomePage,
|
||||||
|
} from './common';
|
||||||
import { sidebarClick } from './sidebar';
|
import { sidebarClick } from './sidebar';
|
||||||
|
|
||||||
|
export const TAG_INVALID_NAMES = {
|
||||||
|
MIN_LENGTH: 'c',
|
||||||
|
MAX_LENGTH: 'a87439625b1c2d3e4f5061728394a5b6c7d8e90a1b2c3d4e5f67890ab',
|
||||||
|
WITH_SPECIAL_CHARS: '!@#$%^&*()',
|
||||||
|
};
|
||||||
|
|
||||||
export const visitClassificationPage = async (
|
export const visitClassificationPage = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
classificationName: string
|
classificationName: string
|
||||||
@ -27,3 +37,53 @@ export const visitClassificationPage = async (
|
|||||||
await classificationResponse;
|
await classificationResponse;
|
||||||
await page.getByRole('menuitem', { name: classificationName }).click();
|
await page.getByRole('menuitem', { name: classificationName }).click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function submitForm(page: Page) {
|
||||||
|
await page.locator('button[type="submit"]').scrollIntoViewIfNeeded();
|
||||||
|
await page.locator('button[type="submit"]').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateForm(page: Page) {
|
||||||
|
// submit form without any data to trigger validation
|
||||||
|
await submitForm(page);
|
||||||
|
|
||||||
|
// error messages
|
||||||
|
await expect(page.locator('#tags_name_help')).toBeVisible();
|
||||||
|
await expect(page.locator('#tags_name_help')).toContainText(
|
||||||
|
'Name is required'
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(page.locator('#tags_description_help')).toBeVisible();
|
||||||
|
await expect(page.locator('#tags_description_help')).toContainText(
|
||||||
|
'Description is required'
|
||||||
|
);
|
||||||
|
|
||||||
|
// validation should work for invalid names
|
||||||
|
|
||||||
|
// min length validation
|
||||||
|
await page.locator('[data-testid="name"]').scrollIntoViewIfNeeded();
|
||||||
|
await page.locator('[data-testid="name"]').clear();
|
||||||
|
await page.locator('[data-testid="name"]').fill(TAG_INVALID_NAMES.MIN_LENGTH);
|
||||||
|
|
||||||
|
await expect(page.locator('#tags_name_help')).toContainText(
|
||||||
|
NAME_MIN_MAX_LENGTH_VALIDATION_ERROR
|
||||||
|
);
|
||||||
|
|
||||||
|
// max length validation
|
||||||
|
await page.locator('[data-testid="name"]').clear();
|
||||||
|
await page.locator('[data-testid="name"]').fill(TAG_INVALID_NAMES.MAX_LENGTH);
|
||||||
|
|
||||||
|
await expect(page.locator('#tags_name_help')).toContainText(
|
||||||
|
NAME_MIN_MAX_LENGTH_VALIDATION_ERROR
|
||||||
|
);
|
||||||
|
|
||||||
|
// with special char validation
|
||||||
|
await page.locator('[data-testid="name"]').clear();
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="name"]')
|
||||||
|
.fill(TAG_INVALID_NAMES.WITH_SPECIAL_CHARS);
|
||||||
|
|
||||||
|
await expect(page.locator('#tags_name_help')).toContainText(
|
||||||
|
NAME_VALIDATION_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user