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
This commit is contained in:
Shailesh Parmar 2024-06-14 11:55:59 +05:30 committed by GitHub
parent 55cd180ffa
commit 18ee2b8ff2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 96 additions and 41 deletions

View File

@ -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 // added extra buffer time as deleteWorkflow API can take up to 2 minute or more to send request
cy.wait('@deleteWorkflow', { cy.wait('@deleteWorkflow', {
requestTimeout: 150000, requestTimeout: 150000,
responseTimeout: 50000, responseTimeout: 150000,
}); });
cy.get('.ant-modal-footer > .ant-btn-primary') cy.get('.ant-modal-footer > .ant-btn-primary')
.should('exist') .should('exist')

View File

@ -55,7 +55,6 @@ entities.forEach((EntityClass) => {
await EntityDataClass.preRequisitesForTests(apiContext); await EntityDataClass.preRequisitesForTests(apiContext);
await entity.create(apiContext); await entity.create(apiContext);
await entity.prepareForTests(apiContext);
await afterAction(); await afterAction();
}); });
@ -134,6 +133,10 @@ entities.forEach((EntityClass) => {
// increase timeout as it using single test for multiple steps // increase timeout as it using single test for multiple steps
test.slow(true); 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 () => { await test.step(`Set ${titleText} Custom Property`, async () => {
for (const type of properties) { for (const type of properties) {
await entity.setCustomProperty( 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 }) => { test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser); const { apiContext, afterAction } = await createNewPage(browser);
await entity.cleanup(apiContext);
await entity.delete(apiContext); await entity.delete(apiContext);
await EntityDataClass.postRequisitesForTests(apiContext); await EntityDataClass.postRequisitesForTests(apiContext);
await afterAction(); await afterAction();

View File

@ -24,6 +24,7 @@ import { SearchIndexServiceClass } from '../../support/entity/service/SearchInde
import { StorageServiceClass } from '../../support/entity/service/StorageServiceClass'; import { StorageServiceClass } from '../../support/entity/service/StorageServiceClass';
import { import {
createNewPage, createNewPage,
getApiContext,
getAuthContext, getAuthContext,
getToken, getToken,
redirectToHomePage, redirectToHomePage,
@ -55,7 +56,6 @@ entities.forEach((EntityClass) => {
await EntityDataClass.preRequisitesForTests(apiContext); await EntityDataClass.preRequisitesForTests(apiContext);
await entity.create(apiContext); await entity.create(apiContext);
await entity.prepareForTests(apiContext);
await afterAction(); await afterAction();
}); });
@ -124,6 +124,9 @@ entities.forEach((EntityClass) => {
// increase timeout as it using single test for multiple steps // increase timeout as it using single test for multiple steps
test.slow(true); test.slow(true);
const { apiContext, afterAction } = await getApiContext(page);
await entity.prepareCustomProperty(apiContext);
await test.step(`Set ${titleText} Custom Property`, async () => { await test.step(`Set ${titleText} Custom Property`, async () => {
for (const type of properties) { for (const type of properties) {
await entity.setCustomProperty( 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 }) => { test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser); const { apiContext, afterAction } = await createNewPage(browser);
await entity.cleanup(apiContext);
await entity.delete(apiContext); await entity.delete(apiContext);
await EntityDataClass.postRequisitesForTests(apiContext); await EntityDataClass.postRequisitesForTests(apiContext);
await afterAction(); await afterAction();

View File

@ -34,7 +34,7 @@ export class ContainerClass extends EntityClass {
}, },
}; };
entity = { entity = {
name: `pw.container%${uuid()}`, name: `pw-container-${uuid()}`,
displayName: `pw-container-${uuid()}`, displayName: `pw-container-${uuid()}`,
service: this.service.name, service: this.service.name,
}; };

View File

@ -34,7 +34,7 @@ export class DashboardClass extends EntityClass {
}, },
}; };
entity = { entity = {
name: `pw.dashboard%${uuid()}`, name: `pw-dashboard-${uuid()}`,
displayName: `pw-dashboard-${uuid()}`, displayName: `pw-dashboard-${uuid()}`,
service: this.service.name, service: this.service.name,
}; };

View File

@ -34,7 +34,7 @@ export class DashboardDataModelClass extends EntityClass {
}, },
}; };
entity = { entity = {
name: `pw.dashboard-data-model%${uuid()}`, name: `pw-dashboard-data-model-${uuid()}`,
displayName: `pw-dashboard-data-model-${uuid()}`, displayName: `pw-dashboard-data-model-${uuid()}`,
service: this.service.name, service: this.service.name,
columns: [ columns: [

View File

@ -13,8 +13,8 @@
import { APIRequestContext, Page } from '@playwright/test'; import { APIRequestContext, Page } from '@playwright/test';
import { CustomPropertySupportedEntityList } from '../../constant/customProperty'; import { CustomPropertySupportedEntityList } from '../../constant/customProperty';
import { import {
createCustomPropertyForEntity,
CustomProperty, CustomProperty,
createCustomPropertyForEntity,
setValueForProperty, setValueForProperty,
validateValueForProperty, validateValueForProperty,
} from '../../utils/customProperty'; } from '../../utils/customProperty';
@ -37,15 +37,15 @@ import {
replyAnnouncement, replyAnnouncement,
softDeleteEntity, softDeleteEntity,
unFollowEntity, unFollowEntity,
upVote,
updateDescription, updateDescription,
updateDisplayNameForEntity, updateDisplayNameForEntity,
updateOwner, updateOwner,
upVote,
validateFollowedEntityToWidget, validateFollowedEntityToWidget,
} from '../../utils/entity'; } from '../../utils/entity';
import { Domain } from '../domain/Domain'; import { Domain } from '../domain/Domain';
import { GlossaryTerm } from '../glossary/GlossaryTerm'; import { GlossaryTerm } from '../glossary/GlossaryTerm';
import { EntityTypeEndpoint, ENTITY_PATH } from './Entity.interface'; import { ENTITY_PATH, EntityTypeEndpoint } from './Entity.interface';
export class EntityClass { export class EntityClass {
type: string; type: string;
@ -70,8 +70,7 @@ export class EntityClass {
// Override for entity visit // Override for entity visit
} }
// Prepare for tests async prepareCustomProperty(apiContext: APIRequestContext) {
async prepareForTests(apiContext: APIRequestContext) {
// Create custom property only for supported entities // Create custom property only for supported entities
if (CustomPropertySupportedEntityList.includes(this.endpoint)) { if (CustomPropertySupportedEntityList.includes(this.endpoint)) {
const data = await createCustomPropertyForEntity( 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 // Delete custom property only for supported entities
if (CustomPropertySupportedEntityList.includes(this.endpoint)) { if (CustomPropertySupportedEntityList.includes(this.endpoint)) {
await this.cleanupUser(apiContext); await this.cleanupUser(apiContext);
@ -122,9 +121,9 @@ export class EntityClass {
owner2: string, owner2: string,
type: 'Teams' | 'Users' = 'Users' type: 'Teams' | 'Users' = 'Users'
) { ) {
await addOwner(page, owner1, type, 'data-assets-header'); await addOwner(page, owner1, type, this.endpoint, 'data-assets-header');
await updateOwner(page, owner2, type, 'data-assets-header'); await updateOwner(page, owner2, type, this.endpoint, 'data-assets-header');
await removeOwner(page, 'data-assets-header'); await removeOwner(page, this.endpoint, 'data-assets-header');
} }
async tier(page: Page, tier1: string, tier2: string) { async tier(page: Page, tier1: string, tier2: string) {
@ -233,6 +232,7 @@ export class EntityClass {
propertyName: propertydetails.name, propertyName: propertydetails.name,
value, value,
propertyType: propertydetails.propertyType.name, propertyType: propertydetails.propertyType.name,
endpoint: this.endpoint,
}); });
await validateValueForProperty({ await validateValueForProperty({
page, page,
@ -252,6 +252,7 @@ export class EntityClass {
propertyName: propertydetails.name, propertyName: propertydetails.name,
value, value,
propertyType: propertydetails.propertyType.name, propertyType: propertydetails.propertyType.name,
endpoint: this.endpoint,
}); });
await validateValueForProperty({ await validateValueForProperty({
page, page,

View File

@ -30,7 +30,7 @@ export class MlModelClass extends EntityClass {
}, },
}; };
entity = { entity = {
name: `pw.mlmodel%${uuid()}`, name: `pw-mlmodel-${uuid()}`,
displayName: `pw-mlmodel-${uuid()}`, displayName: `pw-mlmodel-${uuid()}`,
service: this.service.name, service: this.service.name,
algorithm: 'Time Series', algorithm: 'Time Series',

View File

@ -31,7 +31,7 @@ export class PipelineClass extends EntityClass {
}, },
}; };
entity = { entity = {
name: `pw.pipeline%${uuid()}`, name: `pw-pipeline-${uuid()}`,
displayName: `pw-pipeline-${uuid()}`, displayName: `pw-pipeline-${uuid()}`,
service: this.service.name, service: this.service.name,
tasks: [{ name: 'snowflake_task' }], tasks: [{ name: 'snowflake_task' }],

View File

@ -34,7 +34,7 @@ export class SearchIndexClass extends EntityClass {
}, },
}; };
entity = { entity = {
name: `pw.search-index%${uuid()}`, name: `pw-search-index-${uuid()}`,
displayName: `pw-search-index-${uuid()}`, displayName: `pw-search-index-${uuid()}`,
service: this.service.name, service: this.service.name,
fields: [], fields: [],

View File

@ -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}`; private fqn = `${this.service.name}.${this.topicName}`;
entity = { entity = {
name: this.topicName, name: this.topicName,

View File

@ -54,3 +54,16 @@ export const createNewPage = async (browser: Browser) => {
return { page, apiContext, afterAction }; 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 };
};

View File

@ -12,8 +12,8 @@
*/ */
import { APIRequestContext, expect, Page } from '@playwright/test'; import { APIRequestContext, expect, Page } from '@playwright/test';
import { import {
EntityTypeEndpoint,
ENTITY_PATH, ENTITY_PATH,
EntityTypeEndpoint,
} from '../support/entity/Entity.interface'; } from '../support/entity/Entity.interface';
import { UserClass } from '../support/user/UserClass'; import { UserClass } from '../support/user/UserClass';
import { uuid } from './common'; import { uuid } from './common';
@ -52,8 +52,9 @@ export const setValueForProperty = async (data: {
propertyName: string; propertyName: string;
value: string; value: string;
propertyType: 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 page.click('[data-testid="custom_properties"]');
await expect(page.getByRole('cell', { name: propertyName })).toContainText( await expect(page.getByRole('cell', { name: propertyName })).toContainText(
@ -66,6 +67,7 @@ export const setValueForProperty = async (data: {
await editButton.scrollIntoViewIfNeeded(); await editButton.scrollIntoViewIfNeeded();
await editButton.click({ force: true }); await editButton.click({ force: true });
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
switch (propertyType) { switch (propertyType) {
case 'markdown': case 'markdown':
await page await page
@ -97,11 +99,11 @@ export const setValueForProperty = async (data: {
break; break;
case 'enum': case 'enum':
await page.locator('#enumValues').click(); await page.click('#enumValues');
await page.locator('#enumValues').fill(value); await page.fill('#enumValues', value);
await page.locator('#enumValues').press('Enter'); await page.press('#enumValues', 'Enter');
await page.mouse.click(0, 0); await page.mouse.click(0, 0);
await page.locator('[data-testid="inline-save-btn"]').click(); await page.click('[data-testid="inline-save-btn"]');
break; break;
@ -159,6 +161,7 @@ export const setValueForProperty = async (data: {
break; break;
} }
} }
await patchRequest;
}; };
export const validateValueForProperty = async (data: { export const validateValueForProperty = async (data: {
@ -219,8 +222,8 @@ export const getPropertyValues = (
case 'number': case 'number':
return { return {
value: '123', value: '1234',
newValue: '456', newValue: '4567',
}; };
case 'duration': case 'duration':
return { return {
@ -320,8 +323,8 @@ export const createCustomPropertyForEntity = async (
`/api/v1/metadata/types/${entitySchema.id}`, `/api/v1/metadata/types/${entitySchema.id}`,
{ {
data: { data: {
name: `cyCustomProperty${uuid()}`, name: `pwCustomProperty${uuid()}`,
description: `cyCustomProperty${uuid()}`, description: `pwCustomProperty${uuid()}`,
propertyType: { propertyType: {
id: item.id ?? '', id: item.id ?? '',
type: 'type', type: 'type',

View File

@ -31,9 +31,11 @@ export const visitEntityPage = async (data: {
dataTestId: string; dataTestId: string;
}) => { }) => {
const { page, searchTerm, dataTestId } = data; const { page, searchTerm, dataTestId } = data;
const waitForSearchResponse = page.waitForResponse(
'/api/v1/search/query?q=*index=dataAsset*'
);
await page.getByTestId('searchBox').fill(searchTerm); 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(dataTestId).getByTestId('data-name').click();
await page.getByTestId('searchBox').clear(); await page.getByTestId('searchBox').clear();
}; };
@ -42,6 +44,7 @@ export const addOwner = async (
page: Page, page: Page,
owner: string, owner: string,
type: 'Teams' | 'Users' = 'Users', type: 'Teams' | 'Users' = 'Users',
endpoint: EntityTypeEndpoint,
dataTestId?: string dataTestId?: string
) => { ) => {
await page.getByTestId('edit-owner').click(); await page.getByTestId('edit-owner').click();
@ -68,7 +71,9 @@ export const addOwner = async (
await page.waitForResponse( await page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(owner)}*` `/api/v1/search/query?q=*${encodeURIComponent(owner)}*`
); );
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.getByRole('listitem', { name: owner }).click(); await page.getByRole('listitem', { name: owner }).click();
await patchRequest;
await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText( await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText(
owner owner
@ -79,6 +84,7 @@ export const updateOwner = async (
page: Page, page: Page,
owner: string, owner: string,
type: 'Teams' | 'Users' = 'Users', type: 'Teams' | 'Users' = 'Users',
endpoint: EntityTypeEndpoint,
dataTestId?: string dataTestId?: string
) => { ) => {
await page.getByTestId('edit-owner').click(); await page.getByTestId('edit-owner').click();
@ -90,20 +96,29 @@ export const updateOwner = async (
await page.waitForResponse( await page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(owner)}*` `/api/v1/search/query?q=*${encodeURIComponent(owner)}*`
); );
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.getByRole('listitem', { name: owner }).click(); await page.getByRole('listitem', { name: owner }).click();
await patchRequest;
await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText( await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText(
owner 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.getByTestId('edit-owner').click();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
await expect(page.getByTestId('remove-owner').locator('svg')).toBeVisible(); 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 page.getByTestId('remove-owner').locator('svg').click();
await patchRequest;
await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText( await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText(
'No Owner' 'No Owner'
@ -156,6 +171,9 @@ export const assignTag = async (
`/api/v1/search/query?q=*${encodeURIComponent(tag)}*` `/api/v1/search/query?q=*${encodeURIComponent(tag)}*`
); );
await page.getByTestId(`tag-${tag}`).click(); await page.getByTestId(`tag-${tag}`).click();
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click(); await page.getByTestId('saveAssociatedTag').click();
await expect( await expect(
@ -183,6 +201,9 @@ export const removeTag = async (page: Page, tags: string[]) => {
const patchRequest = page.waitForRequest( const patchRequest = page.waitForRequest(
(request) => request.method() === 'PATCH' (request) => request.method() === 'PATCH'
); );
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click(); await page.getByTestId('saveAssociatedTag').click();
await patchRequest; await patchRequest;
@ -217,6 +238,9 @@ export const assignGlossaryTerm = async (
`/api/v1/search/query?q=*${encodeURIComponent(glossaryTerm.displayName)}*` `/api/v1/search/query?q=*${encodeURIComponent(glossaryTerm.displayName)}*`
); );
await page.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`).click(); await page.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`).click();
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click(); await page.getByTestId('saveAssociatedTag').click();
await expect( await expect(
@ -248,6 +272,9 @@ export const removeGlossaryTerm = async (
const patchRequest = page.waitForRequest( const patchRequest = page.waitForRequest(
(request) => request.method() === 'PATCH' (request) => request.method() === 'PATCH'
); );
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click(); await page.getByTestId('saveAssociatedTag').click();
await patchRequest; await patchRequest;
@ -763,11 +790,12 @@ export const softDeleteEntity = async (
await expect(page.locator('.ant-modal-title')).toContainText(displayName); await expect(page.locator('.ant-modal-title')).toContainText(displayName);
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE'); await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
await page.click('[data-testid="confirm-button"]'); const deleteResponse = page.waitForResponse(
await page.waitForResponse(
`/api/v1/${endPoint}/*?hardDelete=false&recursive=true` `/api/v1/${endPoint}/*?hardDelete=false&recursive=true`
); );
await page.click('[data-testid="confirm-button"]');
await deleteResponse;
await expect(page.locator('.Toastify__toast-body')).toHaveText( await expect(page.locator('.Toastify__toast-body')).toHaveText(
/deleted successfully!/ /deleted successfully!/
@ -802,7 +830,7 @@ export const softDeleteEntity = async (
await expect(tableCount).toContainText('1'); await expect(tableCount).toContainText('1');
await page.click(`[data-testid=${entityName}]`); await page.click(`[data-testid="${entityName}"]`);
} }
await restoreEntity(page); await restoreEntity(page);
@ -832,10 +860,11 @@ export const hardDeleteEntity = async (
await page.click('[data-testid="hard-delete-option"]'); await page.click('[data-testid="hard-delete-option"]');
await page.check('[data-testid="hard-delete"]'); await page.check('[data-testid="hard-delete"]');
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE'); await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
await page.click('[data-testid="confirm-button"]'); const deleteResponse = page.waitForResponse(
await page.waitForResponse( `/api/v1/${endPoint}/*?hardDelete=true&recursive=true`
`**/api/v1/${endPoint}/*?hardDelete=true&recursive=true`
); );
await page.click('[data-testid="confirm-button"]');
await deleteResponse;
await expect(page.locator('.Toastify__toast-body')).toHaveText( await expect(page.locator('.Toastify__toast-body')).toHaveText(
/deleted successfully!/ /deleted successfully!/