From 18ee2b8ff22ae9be26fee866ac68cb68ebd56c8e Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Fri, 14 Jun 2024 11:55:59 +0530 Subject: [PATCH] playwright: improve the existing playwright test (#16650) * playwright: improve the existing playwright test * pw improvement * minor change * reduce the scope of custom property * reduce scope of custom property test * addressing comments * updated response timeout --- .../ui/cypress/common/Utils/Services.ts | 2 +- .../ui/playwright/e2e/Pages/Entity.spec.ts | 8 ++- .../e2e/Pages/ServiceEntity.spec.ts | 9 +++- .../support/entity/ContainerClass.ts | 2 +- .../support/entity/DashboardClass.ts | 2 +- .../support/entity/DashboardDataModelClass.ts | 2 +- .../playwright/support/entity/EntityClass.ts | 19 +++---- .../playwright/support/entity/MlModelClass.ts | 2 +- .../support/entity/PipelineClass.ts | 2 +- .../support/entity/SearchIndexClass.ts | 2 +- .../playwright/support/entity/TopicClass.ts | 2 +- .../resources/ui/playwright/utils/common.ts | 13 +++++ .../ui/playwright/utils/customProperty.ts | 23 +++++---- .../resources/ui/playwright/utils/entity.ts | 49 +++++++++++++++---- 14 files changed, 96 insertions(+), 41 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Services.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Services.ts index e444a09f250..0a79dbcebbf 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Services.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Services.ts @@ -196,7 +196,7 @@ export const testConnection = () => { // added extra buffer time as deleteWorkflow API can take up to 2 minute or more to send request cy.wait('@deleteWorkflow', { requestTimeout: 150000, - responseTimeout: 50000, + responseTimeout: 150000, }); cy.get('.ant-modal-footer > .ant-btn-primary') .should('exist') diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts index 6cbccd6e60c..a36549d682e 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts @@ -55,7 +55,6 @@ entities.forEach((EntityClass) => { await EntityDataClass.preRequisitesForTests(apiContext); await entity.create(apiContext); - await entity.prepareForTests(apiContext); await afterAction(); }); @@ -134,6 +133,10 @@ entities.forEach((EntityClass) => { // increase timeout as it using single test for multiple steps test.slow(true); + const token = await getToken(page); + const apiContext = await getAuthContext(token); + await entity.prepareCustomProperty(apiContext); + await test.step(`Set ${titleText} Custom Property`, async () => { for (const type of properties) { await entity.setCustomProperty( @@ -153,6 +156,8 @@ entities.forEach((EntityClass) => { ); } }); + + await entity.cleanupCustomProperty(apiContext); }); } @@ -162,7 +167,6 @@ entities.forEach((EntityClass) => { test.afterAll('Cleanup', async ({ browser }) => { const { apiContext, afterAction } = await createNewPage(browser); - await entity.cleanup(apiContext); await entity.delete(apiContext); await EntityDataClass.postRequisitesForTests(apiContext); await afterAction(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts index f9d59399923..1bc144850e0 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts @@ -24,6 +24,7 @@ import { SearchIndexServiceClass } from '../../support/entity/service/SearchInde import { StorageServiceClass } from '../../support/entity/service/StorageServiceClass'; import { createNewPage, + getApiContext, getAuthContext, getToken, redirectToHomePage, @@ -55,7 +56,6 @@ entities.forEach((EntityClass) => { await EntityDataClass.preRequisitesForTests(apiContext); await entity.create(apiContext); - await entity.prepareForTests(apiContext); await afterAction(); }); @@ -124,6 +124,9 @@ entities.forEach((EntityClass) => { // increase timeout as it using single test for multiple steps test.slow(true); + const { apiContext, afterAction } = await getApiContext(page); + await entity.prepareCustomProperty(apiContext); + await test.step(`Set ${titleText} Custom Property`, async () => { for (const type of properties) { await entity.setCustomProperty( @@ -143,6 +146,9 @@ entities.forEach((EntityClass) => { ); } }); + + await entity.cleanupCustomProperty(apiContext); + await afterAction(); }); } @@ -152,7 +158,6 @@ entities.forEach((EntityClass) => { test.afterAll('Cleanup', async ({ browser }) => { const { apiContext, afterAction } = await createNewPage(browser); - await entity.cleanup(apiContext); await entity.delete(apiContext); await EntityDataClass.postRequisitesForTests(apiContext); await afterAction(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts index bf9934e68b6..4f13faab097 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts @@ -34,7 +34,7 @@ export class ContainerClass extends EntityClass { }, }; entity = { - name: `pw.container%${uuid()}`, + name: `pw-container-${uuid()}`, displayName: `pw-container-${uuid()}`, service: this.service.name, }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts index bc42a4e7180..49e64ede71c 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts @@ -34,7 +34,7 @@ export class DashboardClass extends EntityClass { }, }; entity = { - name: `pw.dashboard%${uuid()}`, + name: `pw-dashboard-${uuid()}`, displayName: `pw-dashboard-${uuid()}`, service: this.service.name, }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts index b84d39baf02..bb0a4d156b8 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts @@ -34,7 +34,7 @@ export class DashboardDataModelClass extends EntityClass { }, }; entity = { - name: `pw.dashboard-data-model%${uuid()}`, + name: `pw-dashboard-data-model-${uuid()}`, displayName: `pw-dashboard-data-model-${uuid()}`, service: this.service.name, columns: [ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts index eabacd5fc52..8a49d1e74c3 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts @@ -13,8 +13,8 @@ import { APIRequestContext, Page } from '@playwright/test'; import { CustomPropertySupportedEntityList } from '../../constant/customProperty'; import { - createCustomPropertyForEntity, CustomProperty, + createCustomPropertyForEntity, setValueForProperty, validateValueForProperty, } from '../../utils/customProperty'; @@ -37,15 +37,15 @@ import { replyAnnouncement, softDeleteEntity, unFollowEntity, + upVote, updateDescription, updateDisplayNameForEntity, updateOwner, - upVote, validateFollowedEntityToWidget, } from '../../utils/entity'; import { Domain } from '../domain/Domain'; import { GlossaryTerm } from '../glossary/GlossaryTerm'; -import { EntityTypeEndpoint, ENTITY_PATH } from './Entity.interface'; +import { ENTITY_PATH, EntityTypeEndpoint } from './Entity.interface'; export class EntityClass { type: string; @@ -70,8 +70,7 @@ export class EntityClass { // Override for entity visit } - // Prepare for tests - async prepareForTests(apiContext: APIRequestContext) { + async prepareCustomProperty(apiContext: APIRequestContext) { // Create custom property only for supported entities if (CustomPropertySupportedEntityList.includes(this.endpoint)) { const data = await createCustomPropertyForEntity( @@ -84,7 +83,7 @@ export class EntityClass { } } - async cleanup(apiContext: APIRequestContext) { + async cleanupCustomProperty(apiContext: APIRequestContext) { // Delete custom property only for supported entities if (CustomPropertySupportedEntityList.includes(this.endpoint)) { await this.cleanupUser(apiContext); @@ -122,9 +121,9 @@ export class EntityClass { owner2: string, type: 'Teams' | 'Users' = 'Users' ) { - await addOwner(page, owner1, type, 'data-assets-header'); - await updateOwner(page, owner2, type, 'data-assets-header'); - await removeOwner(page, 'data-assets-header'); + await addOwner(page, owner1, type, this.endpoint, 'data-assets-header'); + await updateOwner(page, owner2, type, this.endpoint, 'data-assets-header'); + await removeOwner(page, this.endpoint, 'data-assets-header'); } async tier(page: Page, tier1: string, tier2: string) { @@ -233,6 +232,7 @@ export class EntityClass { propertyName: propertydetails.name, value, propertyType: propertydetails.propertyType.name, + endpoint: this.endpoint, }); await validateValueForProperty({ page, @@ -252,6 +252,7 @@ export class EntityClass { propertyName: propertydetails.name, value, propertyType: propertydetails.propertyType.name, + endpoint: this.endpoint, }); await validateValueForProperty({ page, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts index 59bba510322..ab1add15c60 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts @@ -30,7 +30,7 @@ export class MlModelClass extends EntityClass { }, }; entity = { - name: `pw.mlmodel%${uuid()}`, + name: `pw-mlmodel-${uuid()}`, displayName: `pw-mlmodel-${uuid()}`, service: this.service.name, algorithm: 'Time Series', diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts index 26f39111604..4c830e25b22 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts @@ -31,7 +31,7 @@ export class PipelineClass extends EntityClass { }, }; entity = { - name: `pw.pipeline%${uuid()}`, + name: `pw-pipeline-${uuid()}`, displayName: `pw-pipeline-${uuid()}`, service: this.service.name, tasks: [{ name: 'snowflake_task' }], diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts index 2dd22aec13a..40985e9e962 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts @@ -34,7 +34,7 @@ export class SearchIndexClass extends EntityClass { }, }; entity = { - name: `pw.search-index%${uuid()}`, + name: `pw-search-index-${uuid()}`, displayName: `pw-search-index-${uuid()}`, service: this.service.name, fields: [], diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TopicClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TopicClass.ts index 38a0c0949fb..4157c66e286 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TopicClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TopicClass.ts @@ -31,7 +31,7 @@ export class TopicClass extends EntityClass { }, }, }; - private topicName = `pw.topic%${uuid()}`; + private topicName = `pw-topic-${uuid()}`; private fqn = `${this.service.name}.${this.topicName}`; entity = { name: this.topicName, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts index a34fcadb69e..d232933347f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts @@ -54,3 +54,16 @@ export const createNewPage = async (browser: Browser) => { return { page, apiContext, afterAction }; }; + +/** + * Retrieves the API context for the given page. + * @param page The Playwright page object. + * @returns An object containing the API context and a cleanup function. + */ +export const getApiContext = async (page: Page) => { + const token = await getToken(page); + const apiContext = await getAuthContext(token); + const afterAction = async () => await apiContext.dispose(); + + return { apiContext, afterAction }; +}; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts index 0c4526c598c..c6cf6b1d9a0 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts @@ -12,8 +12,8 @@ */ import { APIRequestContext, expect, Page } from '@playwright/test'; import { - EntityTypeEndpoint, ENTITY_PATH, + EntityTypeEndpoint, } from '../support/entity/Entity.interface'; import { UserClass } from '../support/user/UserClass'; import { uuid } from './common'; @@ -52,8 +52,9 @@ export const setValueForProperty = async (data: { propertyName: string; value: string; propertyType: string; + endpoint: EntityTypeEndpoint; }) => { - const { page, propertyName, value, propertyType } = data; + const { page, propertyName, value, propertyType, endpoint } = data; await page.click('[data-testid="custom_properties"]'); await expect(page.getByRole('cell', { name: propertyName })).toContainText( @@ -66,6 +67,7 @@ export const setValueForProperty = async (data: { await editButton.scrollIntoViewIfNeeded(); await editButton.click({ force: true }); + const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`); switch (propertyType) { case 'markdown': await page @@ -97,11 +99,11 @@ export const setValueForProperty = async (data: { break; case 'enum': - await page.locator('#enumValues').click(); - await page.locator('#enumValues').fill(value); - await page.locator('#enumValues').press('Enter'); + await page.click('#enumValues'); + await page.fill('#enumValues', value); + await page.press('#enumValues', 'Enter'); await page.mouse.click(0, 0); - await page.locator('[data-testid="inline-save-btn"]').click(); + await page.click('[data-testid="inline-save-btn"]'); break; @@ -159,6 +161,7 @@ export const setValueForProperty = async (data: { break; } } + await patchRequest; }; export const validateValueForProperty = async (data: { @@ -219,8 +222,8 @@ export const getPropertyValues = ( case 'number': return { - value: '123', - newValue: '456', + value: '1234', + newValue: '4567', }; case 'duration': return { @@ -320,8 +323,8 @@ export const createCustomPropertyForEntity = async ( `/api/v1/metadata/types/${entitySchema.id}`, { data: { - name: `cyCustomProperty${uuid()}`, - description: `cyCustomProperty${uuid()}`, + name: `pwCustomProperty${uuid()}`, + description: `pwCustomProperty${uuid()}`, propertyType: { id: item.id ?? '', type: 'type', diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts index 43aefbbd15b..3dbbf2810ba 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts @@ -31,9 +31,11 @@ export const visitEntityPage = async (data: { dataTestId: string; }) => { const { page, searchTerm, dataTestId } = data; - + const waitForSearchResponse = page.waitForResponse( + '/api/v1/search/query?q=*index=dataAsset*' + ); await page.getByTestId('searchBox').fill(searchTerm); - await page.waitForResponse('/api/v1/search/query?q=*index=dataAsset*'); + await waitForSearchResponse; await page.getByTestId(dataTestId).getByTestId('data-name').click(); await page.getByTestId('searchBox').clear(); }; @@ -42,6 +44,7 @@ export const addOwner = async ( page: Page, owner: string, type: 'Teams' | 'Users' = 'Users', + endpoint: EntityTypeEndpoint, dataTestId?: string ) => { await page.getByTestId('edit-owner').click(); @@ -68,7 +71,9 @@ export const addOwner = async ( await page.waitForResponse( `/api/v1/search/query?q=*${encodeURIComponent(owner)}*` ); + const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`); await page.getByRole('listitem', { name: owner }).click(); + await patchRequest; await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText( owner @@ -79,6 +84,7 @@ export const updateOwner = async ( page: Page, owner: string, type: 'Teams' | 'Users' = 'Users', + endpoint: EntityTypeEndpoint, dataTestId?: string ) => { await page.getByTestId('edit-owner').click(); @@ -90,20 +96,29 @@ export const updateOwner = async ( await page.waitForResponse( `/api/v1/search/query?q=*${encodeURIComponent(owner)}*` ); + + const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`); await page.getByRole('listitem', { name: owner }).click(); + await patchRequest; await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText( owner ); }; -export const removeOwner = async (page: Page, dataTestId?: string) => { +export const removeOwner = async ( + page: Page, + endpoint: EntityTypeEndpoint, + dataTestId?: string +) => { await page.getByTestId('edit-owner').click(); await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); await expect(page.getByTestId('remove-owner').locator('svg')).toBeVisible(); + const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`); await page.getByTestId('remove-owner').locator('svg').click(); + await patchRequest; await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText( 'No Owner' @@ -156,6 +171,9 @@ export const assignTag = async ( `/api/v1/search/query?q=*${encodeURIComponent(tag)}*` ); await page.getByTestId(`tag-${tag}`).click(); + + await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); + await page.getByTestId('saveAssociatedTag').click(); await expect( @@ -183,6 +201,9 @@ export const removeTag = async (page: Page, tags: string[]) => { const patchRequest = page.waitForRequest( (request) => request.method() === 'PATCH' ); + + await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); + await page.getByTestId('saveAssociatedTag').click(); await patchRequest; @@ -217,6 +238,9 @@ export const assignGlossaryTerm = async ( `/api/v1/search/query?q=*${encodeURIComponent(glossaryTerm.displayName)}*` ); await page.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`).click(); + + await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); + await page.getByTestId('saveAssociatedTag').click(); await expect( @@ -248,6 +272,9 @@ export const removeGlossaryTerm = async ( const patchRequest = page.waitForRequest( (request) => request.method() === 'PATCH' ); + + await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); + await page.getByTestId('saveAssociatedTag').click(); await patchRequest; @@ -763,11 +790,12 @@ export const softDeleteEntity = async ( await expect(page.locator('.ant-modal-title')).toContainText(displayName); await page.fill('[data-testid="confirmation-text-input"]', 'DELETE'); - await page.click('[data-testid="confirm-button"]'); - - await page.waitForResponse( + const deleteResponse = page.waitForResponse( `/api/v1/${endPoint}/*?hardDelete=false&recursive=true` ); + await page.click('[data-testid="confirm-button"]'); + + await deleteResponse; await expect(page.locator('.Toastify__toast-body')).toHaveText( /deleted successfully!/ @@ -802,7 +830,7 @@ export const softDeleteEntity = async ( await expect(tableCount).toContainText('1'); - await page.click(`[data-testid=${entityName}]`); + await page.click(`[data-testid="${entityName}"]`); } await restoreEntity(page); @@ -832,10 +860,11 @@ export const hardDeleteEntity = async ( await page.click('[data-testid="hard-delete-option"]'); await page.check('[data-testid="hard-delete"]'); await page.fill('[data-testid="confirmation-text-input"]', 'DELETE'); - await page.click('[data-testid="confirm-button"]'); - await page.waitForResponse( - `**/api/v1/${endPoint}/*?hardDelete=true&recursive=true` + const deleteResponse = page.waitForResponse( + `/api/v1/${endPoint}/*?hardDelete=true&recursive=true` ); + await page.click('[data-testid="confirm-button"]'); + await deleteResponse; await expect(page.locator('.Toastify__toast-body')).toHaveText( /deleted successfully!/