From ae9de3057d11096ced5ece184394415f7ce19045 Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Mon, 9 Sep 2024 11:48:27 +0530 Subject: [PATCH] Migrate: tags spec to playwright (#17758) --- .../ui/cypress/e2e/Pages/Tags.spec.ts | 379 ----------------- .../ui/playwright/e2e/Pages/Tags.spec.ts | 382 ++++++++++++++++++ .../main/resources/ui/playwright/utils/tag.ts | 64 ++- 3 files changed, 444 insertions(+), 381 deletions(-) delete mode 100644 openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.ts create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.ts b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.ts deleted file mode 100644 index 616582008a1..00000000000 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts new file mode 100644 index 00000000000..48e38f724f7 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts @@ -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(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/tag.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/tag.ts index 6d5e32682f1..a834a852115 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/tag.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/tag.ts @@ -10,11 +10,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Page } from '@playwright/test'; +import { expect, Page } from '@playwright/test'; 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'; +export const TAG_INVALID_NAMES = { + MIN_LENGTH: 'c', + MAX_LENGTH: 'a87439625b1c2d3e4f5061728394a5b6c7d8e90a1b2c3d4e5f67890ab', + WITH_SPECIAL_CHARS: '!@#$%^&*()', +}; + export const visitClassificationPage = async ( page: Page, classificationName: string @@ -27,3 +37,53 @@ export const visitClassificationPage = async ( await classificationResponse; 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 + ); +}