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
cy.wait('@deleteWorkflow', {
requestTimeout: 150000,
responseTimeout: 50000,
responseTimeout: 150000,
});
cy.get('.ant-modal-footer > .ant-btn-primary')
.should('exist')

View File

@ -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();

View File

@ -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();

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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: [

View File

@ -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,

View File

@ -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',

View File

@ -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' }],

View File

@ -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: [],

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

View File

@ -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 };
};

View File

@ -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',

View File

@ -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!/