fix(ui): Flaky ServiceEntity And Entity Playwright (#23162)

* fix flaky serviceEntity and entity

* added new testid

* fix domain list flakyness

* fix flaky user as owner in unsorted list

* addressed comments

* fixed flaky tests from entity spec

* removed unwanted code

* removed unwanted code

* added playwright config file for debugging cl issue

* removed playwright file

(cherry picked from commit 70d9a1182edf9cc9ac4a622eb54bf21f10827072)
This commit is contained in:
Dhruv Parmar 2025-09-04 20:20:05 +05:30 committed by OpenMetadata Release Bot
parent b4ba346d85
commit dd3998948f
5 changed files with 120 additions and 30 deletions

View File

@ -130,13 +130,22 @@ export class DatabaseSchemaClass extends EntityClass {
false false
); );
await page.waitForLoadState('networkidle');
// Wait for the database to be visible before clicking
await page.getByTestId(this.database.name).waitFor({ state: 'visible' });
const databaseResponse = page.waitForResponse( const databaseResponse = page.waitForResponse(
`/api/v1/databases/name/*${this.database.name}?**` `/api/v1/databases/name/*${this.database.name}?**`
); );
await page.getByTestId(this.database.name).click(); await page.getByTestId(this.database.name).click();
await databaseResponse; await databaseResponse;
// Wait for database schema to be visible
await page.getByTestId(this.entity.name).waitFor({ state: 'visible' });
const databaseSchemaResponse = page.waitForResponse( const databaseSchemaResponse = page.waitForResponse(
`/api/v1/databaseSchemas/name/*${this.entity}?*` `/api/v1/databaseSchemas/name/*${this.entity.name}?*`
); );
await page.getByTestId(this.entity.name).click(); await page.getByTestId(this.entity.name).click();
await databaseSchemaResponse; await databaseSchemaResponse;

View File

@ -170,22 +170,31 @@ export const assignDomain = async (
) => { ) => {
await page.getByTestId('add-domain').click(); await page.getByTestId('add-domain').click();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
const searchDomain = page.waitForResponse( const searchDomain = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(domain.name)}*` `/api/v1/search/query?q=*${encodeURIComponent(domain.name)}*`
); );
await page await page
.getByTestId('domain-selectable-tree') .getByTestId('domain-selectable-tree')
.getByTestId('searchbar') .getByTestId('searchbar')
.fill(domain.name); .fill(domain.name);
await searchDomain; await searchDomain;
await page.getByTestId(`tag-${domain.fullyQualifiedName}`).click(); // Wait for the tag element to be visible and ensure page is still valid
const tagSelector = page.getByTestId(`tag-${domain.fullyQualifiedName}`);
await tagSelector.waitFor({ state: 'visible' });
await tagSelector.click();
const patchReq = page.waitForResponse( const patchReq = page.waitForResponse(
(req) => req.request().method() === 'PATCH' (req) => req.request().method() === 'PATCH'
); );
await page.getByTestId('saveAssociatedTag').click(); await page
.getByTestId('domain-selectable-tree')
.getByTestId('saveAssociatedTag')
.click();
await patchReq; await patchReq;
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
@ -221,7 +230,10 @@ export const updateDomain = async (
(req) => req.request().method() === 'PATCH' (req) => req.request().method() === 'PATCH'
); );
await page.getByTestId('saveAssociatedTag').click(); await page
.getByTestId('domain-selectable-tree')
.getByTestId('saveAssociatedTag')
.click();
await patchReq; await patchReq;
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
@ -247,7 +259,10 @@ export const removeDomain = async (
(req) => req.request().method() === 'PATCH' (req) => req.request().method() === 'PATCH'
); );
await page.getByTestId('saveAssociatedTag').click(); await page
.getByTestId('domain-selectable-tree')
.getByTestId('saveAssociatedTag')
.click();
await patchReq; await patchReq;
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
@ -281,13 +296,20 @@ export const assignDataProduct = async (
await searchDataProduct; await searchDataProduct;
await page.getByTestId(`tag-${dataProduct.fullyQualifiedName}`).click(); await page.getByTestId(`tag-${dataProduct.fullyQualifiedName}`).click();
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); await expect(
page
.getByTestId('data-product-dropdown-actions')
.getByTestId('saveAssociatedTag')
).toBeEnabled();
const patchReq = page.waitForResponse( const patchReq = page.waitForResponse(
(req) => req.request().method() === 'PATCH' (req) => req.request().method() === 'PATCH'
); );
await page.getByTestId('saveAssociatedTag').click(); await page
.getByTestId('data-product-dropdown-actions')
.getByTestId('saveAssociatedTag')
.click();
await patchReq; await patchReq;
await expect( await expect(
@ -320,13 +342,20 @@ export const removeDataProduct = async (
.locator('svg') .locator('svg')
.click(); .click();
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); await expect(
page
.getByTestId('data-product-dropdown-actions')
.getByTestId('saveAssociatedTag')
).toBeEnabled();
const patchReq = page.waitForResponse( const patchReq = page.waitForResponse(
(req) => req.request().method() === 'PATCH' (req) => req.request().method() === 'PATCH'
); );
await page.getByTestId('saveAssociatedTag').click(); await page
.getByTestId('data-product-dropdown-actions')
.getByTestId('saveAssociatedTag')
.click();
await patchReq; await patchReq;
await expect( await expect(

View File

@ -94,7 +94,10 @@ export const visitEntityPage = async (data: {
await waitForSearchResponse; await waitForSearchResponse;
await page.getByTestId(dataTestId).getByTestId('data-name').click(); await page.getByTestId(dataTestId).getByTestId('data-name').click();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
await page.getByTestId('searchBox').clear(); await page.getByTestId('searchBox').clear();
}; };
@ -144,8 +147,13 @@ export const addOwner = async ({
await page.getByRole('listitem', { name: owner, exact: true }).click(); await page.getByRole('listitem', { name: owner, exact: true }).click();
await patchRequest; await patchRequest;
} else { } else {
await page.getByRole('listitem', { name: owner, exact: true }).click(); const ownerItem = page.getByRole('listitem', {
name: owner,
exact: true,
});
await ownerItem.waitFor({ state: 'visible' });
await ownerItem.click();
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`); const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.getByTestId('selectable-list-update-btn').click(); await page.getByTestId('selectable-list-update-btn').click();
await patchRequest; await patchRequest;
@ -260,9 +268,10 @@ export const removeOwner = async ({
await patchRequest; await patchRequest;
await expect( await page
page.getByTestId(dataTestId ?? 'owner-link').getByTestId(ownerName) .getByTestId(dataTestId ?? 'owner-link')
).not.toBeVisible(); .getByTestId(ownerName)
.waitFor({ state: 'hidden' });
}; };
export const addMultiOwner = async (data: { export const addMultiOwner = async (data: {
@ -345,6 +354,9 @@ export const addMultiOwner = async (data: {
name: ownerName, name: ownerName,
exact: true, exact: true,
}); });
await ownerItem.waitFor({ state: 'visible' });
// Wait for the item to exist and be visible before clicking
if (type === 'Teams') { if (type === 'Teams') {
if (isSelectableInsideForm) { if (isSelectableInsideForm) {
@ -544,11 +556,19 @@ export const assignTag = async (
.getByTestId(action === 'Add' ? 'add-tag' : 'edit-button') .getByTestId(action === 'Add' ? 'add-tag' : 'edit-button')
.click(); .click();
// Wait for the form to be visible and stable
await page.locator('#tagsForm_tags').waitFor({
state: 'visible',
});
const searchTags = page.waitForResponse( const searchTags = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(tag)}*` `/api/v1/search/query?q=*${encodeURIComponent(tag)}*`
); );
await page.locator('#tagsForm_tags').fill(tag); await page.locator('#tagsForm_tags').fill(tag);
await searchTags; await searchTags;
await page await page
.getByTestId(`tag-${tagFqn ? `${tagFqn}` : tag}`) .getByTestId(`tag-${tagFqn ? `${tagFqn}` : tag}`)
.first() .first()
@ -742,13 +762,16 @@ export const assignGlossaryTerm = async (
.getByTestId('glossary-container') .getByTestId('glossary-container')
.getByTestId(action === 'Add' ? 'add-tag' : 'edit-button') .getByTestId(action === 'Add' ? 'add-tag' : 'edit-button')
.click(); .click();
const searchGlossaryTerm = page.waitForResponse( const searchGlossaryTerm = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(glossaryTerm.displayName)}*` `/api/v1/search/query?q=*${encodeURIComponent(glossaryTerm.displayName)}*`
); );
// Wait for the form to be visible before proceeding
await page.locator('#tagsForm_tags').waitFor({ state: 'visible' });
// Fill the input first
await page.locator('#tagsForm_tags').fill(glossaryTerm.displayName); await page.locator('#tagsForm_tags').fill(glossaryTerm.displayName);
await searchGlossaryTerm; await searchGlossaryTerm;
await page.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`).click(); await page.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`).click();
await page.waitForSelector( await page.waitForSelector(
@ -756,11 +779,18 @@ export const assignGlossaryTerm = async (
{ state: 'visible' } { state: 'visible' }
); );
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); await expect(
page.getByTestId('custom-drop-down-menu').getByTestId('saveAssociatedTag')
).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click(); await page
.getByTestId('custom-drop-down-menu')
.getByTestId('saveAssociatedTag')
.click();
await expect(page.getByTestId('saveAssociatedTag')).not.toBeVisible(); await expect(
page.getByTestId('custom-drop-down-menu').getByTestId('saveAssociatedTag')
).not.toBeVisible();
await expect( await expect(
page page
@ -861,9 +891,14 @@ export const removeGlossaryTerm = async (
{ state: 'visible' } { state: 'visible' }
); );
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); await expect(
page.getByTestId('custom-drop-down-menu').getByTestId('saveAssociatedTag')
).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click(); await page
.getByTestId('custom-drop-down-menu')
.getByTestId('saveAssociatedTag')
.click();
await patchRequest; await patchRequest;
await expect( await expect(
@ -968,14 +1003,18 @@ export const unFollowEntity = async (
page: Page, page: Page,
endpoint: EntityTypeEndpoint endpoint: EntityTypeEndpoint
) => { ) => {
await page.waitForLoadState('networkidle');
const followButton = page.getByTestId('entity-follow-button');
await followButton.waitFor({ state: 'visible' });
await expect(followButton).toContainText('Unfollow');
const unFollowResponse = page.waitForResponse( const unFollowResponse = page.waitForResponse(
`/api/v1/${endpoint}/*/followers/*` `/api/v1/${endpoint}/*/followers/*`
); );
await page.waitForLoadState('networkidle'); await followButton.click();
await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
await page.getByTestId('entity-follow-button').click();
await unFollowResponse; await unFollowResponse;
await expect(page.getByTestId('entity-follow-button')).toContainText( await expect(page.getByTestId('entity-follow-button')).toContainText(
@ -1346,9 +1385,16 @@ export const removeDisplayNameForEntityChildren = async (
await page.locator('#displayName').fill(''); await page.locator('#displayName').fill('');
const updateRequest = page.waitForResponse((req) => const updateRequest = page.waitForResponse((response) => {
['PUT', 'PATCH'].includes(req.request().method()) const method = response.request().method();
const url = response.url();
// Check Analytics Api Does Not Intterupt With PUT CAll
return (
(method === 'PUT' || method === 'PATCH') &&
!url.includes('api/v1/analytics/web/events/collect')
); );
});
await page.click('[data-testid="save-button"]'); await page.click('[data-testid="save-button"]');
await updateRequest; await updateRequest;

View File

@ -128,7 +128,10 @@ const DataProductsSelectList = ({
<> <>
{menu} {menu}
{hasContentLoading ? <Loader size="small" /> : null} {hasContentLoading ? <Loader size="small" /> : null}
<Space className="p-sm p-b-xss p-l-xs custom-dropdown-render" size={8}> <Space
className="p-sm p-b-xss p-l-xs custom-dropdown-render"
data-testid="data-product-dropdown-actions"
size={8}>
<Button <Button
className="update-btn" className="update-btn"
data-testid="saveAssociatedTag" data-testid="saveAssociatedTag"

View File

@ -141,7 +141,10 @@ const TreeAsyncSelectList: FC<TreeAsyncSelectListProps> = ({
<KeyDownStopPropagationWrapper> <KeyDownStopPropagationWrapper>
<div ref={dropdownContainerRef}> <div ref={dropdownContainerRef}>
{isLoading ? <Loader size="small" /> : menu} {isLoading ? <Loader size="small" /> : menu}
<Space className="p-sm p-b-xss p-l-xs custom-dropdown-render" size={8}> <Space
className="p-sm p-b-xss p-l-xs custom-dropdown-render"
data-testid="custom-drop-down-menu"
size={8}>
<Button <Button
className="update-btn" className="update-btn"
data-testid="saveAssociatedTag" data-testid="saveAssociatedTag"