PLAYWRIGHT: fix the bulk action playwright flaky playwright test (#21582)

* fix the bulk action playwright flaky playwright test

* change entity and service.spec user to custom user and add a retry mechnaism for checking soft deleted entity

* try to fix alert not found issue

* remove failure

* fix bot playwright failure

(cherry picked from commit 78f523fd1327a992a39c429281f057b4f770f9fa)
This commit is contained in:
Ashish Gupta 2025-06-08 15:16:52 +05:30 committed by OpenMetadata Release Bot
parent 393d869e93
commit e37b794248
7 changed files with 118 additions and 23 deletions

View File

@ -512,7 +512,7 @@ test.describe('Bulk Edit Entity', () => {
});
test('Table', async ({ page }) => {
test.slow();
test.slow(true);
const tableEntity = new TableClass();

View File

@ -794,6 +794,8 @@ test.describe('Bulk Import Export', () => {
});
test('Table', async ({ page }) => {
test.slow(true);
const tableEntity = new TableClass();
const { apiContext, afterAction } = await getApiContext(page);

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { expect, test } from '@playwright/test';
import { expect, Page, test as base } from '@playwright/test';
import { isUndefined } from 'lodash';
import { CustomPropertySupportedEntityList } from '../../constant/customProperty';
import { ApiEndpointClass } from '../../support/entity/ApiEndpointClass';
@ -26,9 +26,9 @@ import { StoredProcedureClass } from '../../support/entity/StoredProcedureClass'
import { TableClass } from '../../support/entity/TableClass';
import { TopicClass } from '../../support/entity/TopicClass';
import { UserClass } from '../../support/user/UserClass';
import { performAdminLogin } from '../../utils/admin';
import {
assignDomain,
createNewPage,
generateRandomUsername,
getApiContext,
getAuthContext,
@ -59,8 +59,23 @@ const entities = [
MetricClass,
] as const;
// use the admin user to login
test.use({ storageState: 'playwright/.auth/admin.json' });
const adminUser = new UserClass();
const test = base.extend<{ page: Page }>({
page: async ({ browser }, use) => {
const adminPage = await browser.newPage();
await adminUser.login(adminPage);
await use(adminPage);
await adminPage.close();
},
});
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await adminUser.create(apiContext);
await adminUser.setAdminRole(apiContext);
await afterAction();
});
entities.forEach((EntityClass) => {
const entity = new EntityClass();
@ -72,7 +87,7 @@ entities.forEach((EntityClass) => {
entity.type === 'MlModel' ? 'data-testid' : 'data-row-key';
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser);
const { apiContext, afterAction } = await performAdminLogin(browser);
await EntityDataClass.preRequisitesForTests(apiContext);
await entity.create(apiContext);
@ -380,7 +395,7 @@ entities.forEach((EntityClass) => {
test.afterAll('Cleanup', async ({ browser }) => {
test.slow();
const { apiContext, afterAction } = await createNewPage(browser);
const { apiContext, afterAction } = await performAdminLogin(browser);
await entity.delete(apiContext);
await EntityDataClass.postRequisitesForTests(apiContext);
await afterAction();
@ -418,3 +433,9 @@ entities.forEach((EntityClass) => {
});
});
});
test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await adminUser.delete(apiContext);
await afterAction();
});

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { test } from '@playwright/test';
import { Page, test as base } from '@playwright/test';
import { CustomPropertySupportedEntityList } from '../../constant/customProperty';
import { FollowSupportedServices } from '../../constant/service';
import { ApiCollectionClass } from '../../support/entity/ApiCollectionClass';
@ -25,8 +25,9 @@ import { MlmodelServiceClass } from '../../support/entity/service/MlmodelService
import { PipelineServiceClass } from '../../support/entity/service/PipelineServiceClass';
import { SearchIndexServiceClass } from '../../support/entity/service/SearchIndexServiceClass';
import { StorageServiceClass } from '../../support/entity/service/StorageServiceClass';
import { UserClass } from '../../support/user/UserClass';
import { performAdminLogin } from '../../utils/admin';
import {
createNewPage,
getApiContext,
getAuthContext,
getToken,
@ -48,8 +49,23 @@ const entities = [
DatabaseSchemaClass,
] as const;
// use the admin user to login
test.use({ storageState: 'playwright/.auth/admin.json' });
const adminUser = new UserClass();
const test = base.extend<{ page: Page }>({
page: async ({ browser }, use) => {
const adminPage = await browser.newPage();
await adminUser.login(adminPage);
await use(adminPage);
await adminPage.close();
},
});
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await adminUser.create(apiContext);
await adminUser.setAdminRole(apiContext);
await afterAction();
});
entities.forEach((EntityClass) => {
const entity = new EntityClass();
@ -57,7 +73,7 @@ entities.forEach((EntityClass) => {
test.describe(entity.getType(), () => {
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser);
const { apiContext, afterAction } = await performAdminLogin(browser);
await EntityDataClass.preRequisitesForTests(apiContext);
await entity.create(apiContext);
@ -173,7 +189,7 @@ entities.forEach((EntityClass) => {
});
test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await createNewPage(browser);
const { apiContext, afterAction } = await performAdminLogin(browser);
await entity.delete(apiContext);
await EntityDataClass.postRequisitesForTests(apiContext);
await afterAction();
@ -211,3 +227,9 @@ entities.forEach((EntityClass) => {
});
});
});
test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await adminUser.delete(apiContext);
await afterAction();
});

View File

@ -114,7 +114,7 @@ export const deleteBot = async (page: Page) => {
await toastNotification(page, /deleted successfully!/);
await expect(page.getByTestId('page-layout-v1')).not.toContainText(botName);
await expect(page.locator('.ant-table-tbody')).not.toContainText(botName);
};
export const updateBotDetails = async (page: Page) => {

View File

@ -1280,7 +1280,27 @@ export const softDeleteEntity = async (
await page.reload();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
const deletedBadge = page.locator('[data-testid="deleted-badge"]');
// Retry mechanism for checking deleted badge
let deletedBadge = page.locator('[data-testid="deleted-badge"]');
let attempts = 0;
const maxAttempts = 5;
while (attempts < maxAttempts) {
const isVisible = await deletedBadge.isVisible();
if (isVisible) {
break;
}
attempts++;
if (attempts < maxAttempts) {
await page.reload();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
deletedBadge = page.locator('[data-testid="deleted-badge"]');
}
}
await expect(deletedBadge).toHaveText('Deleted');

View File

@ -713,16 +713,46 @@ export const pressKeyXTimes = async (
length: number,
key: string
) => {
for (let i = 0; i < length; i++) {
const activeCell = page.locator('.InovuaReactDataGrid__cell--cell-active');
const isActive = await activeCell.isVisible();
const maxRetries = 3;
const retryDelay = 1000; // 1 second delay between retries
if (!isActive) {
await page.click('.InovuaReactDataGrid__cell--cell-active');
for (let i = 0; i < length; i++) {
let retryCount = 0;
let success = false;
while (!success && retryCount < maxRetries) {
try {
// Wait for the active cell to be visible
const activeCell = page.locator(
'.InovuaReactDataGrid__cell--cell-active'
);
await activeCell.waitFor({ state: 'visible', timeout: 5000 });
// Ensure the cell is focused
if (!(await activeCell.isVisible())) {
await activeCell.click({ timeout: 5000 });
}
// Perform the key press with a longer delay
await activeCell.press(key, { delay: 200 });
// Verify the key press was successful by checking if the cell is still active
await page.waitForTimeout(100); // Small delay to allow for state updates
const isStillActive = await activeCell.isVisible();
if (isStillActive) {
success = true;
} else {
// If cell lost focus, try to regain it
await activeCell.click({ timeout: 5000 });
retryCount++;
await page.waitForTimeout(retryDelay);
}
} catch {
retryCount++;
await page.waitForTimeout(retryDelay);
}
}
await page
.locator('.InovuaReactDataGrid__cell--cell-active')
.press(key, { delay: 100 });
}
};