From e37b794248339a71159b8ac69522bdc153e3876c Mon Sep 17 00:00:00 2001 From: Ashish Gupta Date: Sun, 8 Jun 2025 15:16:52 +0530 Subject: [PATCH] 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) --- .../e2e/Features/BulkEditEntity.spec.ts | 2 +- .../e2e/Features/BulkImport.spec.ts | 2 + .../ui/playwright/e2e/Pages/Entity.spec.ts | 33 ++++++++++--- .../e2e/Pages/ServiceEntity.spec.ts | 34 +++++++++++--- .../main/resources/ui/playwright/utils/bot.ts | 2 +- .../resources/ui/playwright/utils/entity.ts | 22 ++++++++- .../ui/playwright/utils/importUtils.ts | 46 +++++++++++++++---- 7 files changed, 118 insertions(+), 23 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts index 64b7b7758e8..487fffd0b01 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts @@ -512,7 +512,7 @@ test.describe('Bulk Edit Entity', () => { }); test('Table', async ({ page }) => { - test.slow(); + test.slow(true); const tableEntity = new TableClass(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts index 5231cddfa1a..68375de4e31 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts @@ -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); 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 294cd83f8a4..519beec1bdf 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 @@ -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(); +}); 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 f138d821fd1..cf1c89fdf69 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 @@ -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(); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/bot.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/bot.ts index 45a5d1454d1..3b68c36582f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/bot.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/bot.ts @@ -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) => { 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 796856c3637..0d2867816d6 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts @@ -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'); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts index d15bcc4dcfd..883515ce307 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts @@ -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 }); } };