diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityStream.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityStream.spec.ts index 7c7f4149e63a..7f2fe1220bb4 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityStream.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityStream.spec.ts @@ -129,8 +129,6 @@ test.describe('Activity Stream on Entity Pages', () => { await activityFeedTab.click(); await waitForPageLoaded(page); - await page.waitForTimeout(2000); - const messageContainers = page.locator('[data-testid="message-container"]'); const count = await messageContainers.count(); @@ -188,11 +186,12 @@ test.describe('Activity Stream on Entity Pages', () => { await activityFeedTab.click(); await waitForPageLoaded(page); - await page.waitForTimeout(2000); - const allTabInLeftPanel = page.locator( '[data-testid="global-setting-left-panel"]' ); + await allTabInLeftPanel + .waitFor({ state: 'visible', timeout: 2000 }) + .catch(() => undefined); if (await allTabInLeftPanel.isVisible()) { await expect(allTabInLeftPanel).toBeVisible(); 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 7b212b2b82ec..2929a560d691 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 @@ -44,6 +44,7 @@ import { pressKeyXTimes, validateImportStatus, } from '../../utils/importUtils'; +import { waitForSearchIndexed } from '../../utils/polling'; import { visitServiceDetailsPage } from '../../utils/service'; interface GlossaryDetails { @@ -671,6 +672,18 @@ test.describe('Bulk Edit Entity', () => { await glossary.create(apiContext); await glossaryTerm.create(apiContext); + // Wait for the glossary term to be indexed in ES before bulk-edit reads + // the glossary's term list. Otherwise the bulk-edit table comes back + // empty, the test fills row 1 with the term's name, and the system + // creates a new term instead of recognizing the existing one — the + // subsequent status assertion gets "Entity created" instead of + // "Entity updated". + await waitForSearchIndexed( + apiContext, + glossaryTerm.responseData.fullyQualifiedName, + 'glossary_term_search_index' + ); + await test.step('create custom properties for extension edit', async () => { customPropertyRecord = await createCustomPropertiesForEntity( page, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditOperationBadges.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditOperationBadges.spec.ts index 53d035bdfce3..1b44dea5713e 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditOperationBadges.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditOperationBadges.spec.ts @@ -21,6 +21,7 @@ import { GlossaryTerm } from '../../support/glossary/GlossaryTerm'; import { createNewPage, redirectToHomePage } from '../../utils/common'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { fillTextInputDetails, pressKeyXTimes } from '../../utils/importUtils'; +import { waitForSearchIndexed } from '../../utils/polling'; import { visitServiceDetailsPage } from '../../utils/service'; test.use({ storageState: 'playwright/.auth/admin.json' }); @@ -39,6 +40,22 @@ test.describe('BulkEditEntity — OperationBadges and Search (all entity types)' await opGlossary.create(apiContext); await opGlossaryTerm.create(apiContext); await opTable.create(apiContext); + + // Wait for ES indexing of both the term and the table before any test + // opens the bulk-edit view; the bulk-edit table reads from search and + // otherwise comes back empty under load, racing the test's row count + // assertions. + await waitForSearchIndexed( + apiContext, + opGlossaryTerm.responseData.fullyQualifiedName, + 'glossary_term_search_index' + ); + await waitForSearchIndexed( + apiContext, + opTable.entityResponseData.fullyQualifiedName, + 'table_search_index' + ); + await afterAction(); }); 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 ee1511a2b70e..5abe8f3382c5 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 @@ -49,6 +49,7 @@ import { startCsvPreviewAndWaitForGrid, validateImportStatus, } from '../../utils/importUtils'; +import { waitForSearchIndexed } from '../../utils/polling'; // use the admin user to login test.use({ @@ -105,6 +106,15 @@ const expectImportRowStatusesToContain = async ( page: Page, rowStatus: string[] ) => { + // The result grid populates cells asynchronously after Next-click. Without + // first waiting for the row count to match, the toContainText assertion + // can run mid-render against 0 or partial cells, fail under retry too, + // and never recover. Wait for the expected number of detail cells before + // checking text. + await expect(page.locator('.rdg-cell-details')).toHaveCount( + rowStatus.length, + { timeout: 60_000 } + ); await expect(page.locator('.rdg-cell-details')).toContainText(rowStatus); }; @@ -161,6 +171,14 @@ test.describe('Bulk Import Export', () => { const { apiContext, afterAction } = await getApiContext(page); await dbService.create(apiContext); + // Bulk-import reads the service's children list from ES; wait for the + // service to be indexed before the test fetches its export/edit grid. + await waitForSearchIndexed( + apiContext, + dbService.entityResponseData.fullyQualifiedName, + 'database_service_search_index' + ); + await test.step('create custom properties for extension edit', async () => { customPropertyRecord = await createCustomPropertiesForEntity( page, @@ -613,6 +631,14 @@ test.describe('Bulk Import Export', () => { const { apiContext, afterAction } = await getApiContext(page); await dbSchemaEntity.create(apiContext); + // Bulk-import reads the schema's children list from ES; wait for the + // schema to be indexed before the test fetches its export/edit grid. + await waitForSearchIndexed( + apiContext, + dbSchemaEntity.entityResponseData.fullyQualifiedName, + 'database_schema_search_index' + ); + await test.step('create custom properties for extension edit', async () => { customPropertyRecord = await createCustomPropertiesForEntity( page, @@ -775,6 +801,14 @@ test.describe('Bulk Import Export', () => { const { apiContext, afterAction } = await getApiContext(page); await tableEntity.create(apiContext); + // Bulk-import reads the table's columns from ES; wait for the table + // to be indexed before the test fetches its export/edit grid. + await waitForSearchIndexed( + apiContext, + tableEntity.entityResponseData.fullyQualifiedName, + 'table_search_index' + ); + await test.step('should export data table details', async () => { await tableEntity.visitEntityPage(page); await performBulkDownload(page, tableEntity.entity.name); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts index ffedc2485095..af1072dd2495 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts @@ -18,6 +18,7 @@ import { TableClass } from '../../../support/entity/TableClass'; import { UserClass } from '../../../support/user/UserClass'; import { performAdminLogin } from '../../../utils/admin'; import { redirectToHomePage, uuid } from '../../../utils/common'; +import { waitForSearchIndexed } from '../../../utils/polling'; import { cleanupDownloadedCSV, performE2EExportImportFlow, @@ -67,6 +68,18 @@ test.describe( const { apiContext, afterAction } = await performAdminLogin(browser); await table.create(apiContext); await table.createTestCase(apiContext); + + // The export step inside performE2EExportImportFlow reads the table's + // test cases via search. Without waiting for ES indexing here, the + // just-created test case is missing from the exported CSV, so the + // import sees only the 4 added rows (not 5 = 1 existing + 4 added) and + // `processed-row` reports "4" instead of the expected "5". + await waitForSearchIndexed( + apiContext, + table.testCasesResponseData[0]?.fullyQualifiedName, + 'test_case_search_index' + ); + await afterAction(); }); @@ -128,6 +141,16 @@ test.describe( await table.create(apiContext); await table.createTestCase(apiContext); + + // See the matching note in the Admin describe — without waiting for + // ES indexing the export misses the just-created test case and + // processed-row reports "4" instead of "5". + await waitForSearchIndexed( + apiContext, + table.testCasesResponseData[0]?.fullyQualifiedName, + 'test_case_search_index' + ); + await afterAction(); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchIndexNestedColumns.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchIndexNestedColumns.spec.ts index ef8609dd511b..e5a818cb273b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchIndexNestedColumns.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchIndexNestedColumns.spec.ts @@ -116,9 +116,12 @@ test.describe('Search index - deeply nested oversized columns', () => { // Indexing: the table indexed despite the >32 KB nested leaf, so it is found by name. await searchInput.click(); - const byNameResponse = page.waitForResponse('/api/v1/search/query?*'); + await searchInput.fill(table.entity.name); - await byNameResponse; + + await page + .getByTestId(table.service.name + '-' + table.entity.name) + .waitFor({ timeout: 15000 }); await expect(suggestions).toBeVisible(); await expect(suggestions).toContainText(table.entity.name); @@ -126,7 +129,11 @@ test.describe('Search index - deeply nested oversized columns', () => { // Searching: the 25-level-deep column name surfaces the table via columnNamesFuzzy - the // mechanism that replaced the dropped flattened columns.children.name search field. await searchInput.clear(); - const byColumnResponse = page.waitForResponse('/api/v1/search/query?*'); + const byColumnResponse = page.waitForResponse( + (r) => + r.url().includes('/api/v1/search/query') && + r.url().includes(encodeURIComponent(leafColumnName)) + ); await searchInput.fill(leafColumnName); await byColumnResponse; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Table.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Table.spec.ts index b868a6f26f82..d608ae2d8c80 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Table.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Table.spec.ts @@ -58,17 +58,29 @@ test.describe('Table pagination sorting search scenarios ', () => { await page.click('[data-testid="test-cases"]'); await waitForAllLoadersToDisappear(page); + // Capture the paginated list responses so we wait for the *new* data + // before counting rows. Without this, the loader can finish for the + // current page while the page-2 fetch is still in flight, and the + // row count assertion runs against an empty table mid-transition. + const sortedResponse = page.waitForResponse( + '/api/v1/dataQuality/testCases/search/list?*' + ); await page.getByText('Name', { exact: true }).click(); + await sortedResponse; + const nextPageResponse = page.waitForResponse( + '/api/v1/dataQuality/testCases/search/list?*' + ); await page.getByTestId('next').click(); + await nextPageResponse; await waitForAllLoadersToDisappear(page); - expect( - await page - .locator('[data-testid="test-case-table"] tbody tr[data-key]') - .count() - ).toBe(15); + // Use toHaveCount instead of .count() === 15 so Playwright auto-retries + // the assertion until the table re-renders with the page-2 rows. + await expect( + page.locator('[data-testid="test-case-table"] tbody tr[data-key]') + ).toHaveCount(15); }); test('Table search with sorting should work', async ({ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/ActivityFeed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/ActivityFeed.spec.ts index 3595264806d8..afb84c98ebeb 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/ActivityFeed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/ActivityFeed.spec.ts @@ -309,7 +309,6 @@ test.describe('Activity Feed - Filters', () => { await subFilterDropdown.click(); await page.getByRole('menuitem', { name: menuLabel }).click(); await expect(subFilterDropdown).toContainText(new RegExp(menuLabel, 'i')); - await page.waitForTimeout(300); }; await subFilterDropdown.click(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskComments.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskComments.spec.ts index dc7c288ac900..09282bc41ff7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskComments.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskComments.spec.ts @@ -300,8 +300,10 @@ test.describe('Task Comments - @Mention', () => { '.mention-dropdown, .ql-mention-list-container, [data-testid="mention-suggestions"]' ); - // Dropdown should appear (may or may not be visible depending on UI implementation) - await page.waitForTimeout(1000); + await mentionDropdown + .first() + .waitFor({ state: 'visible', timeout: 2000 }) + .catch(() => undefined); } } } @@ -339,12 +341,15 @@ test.describe('Task Comments - @Mention', () => { // Type @ and part of username await page.keyboard.type(`@${mentionedUser.responseData.name}`); - await page.waitForTimeout(500); // Select from dropdown if visible const mentionItem = page.locator( `.mention-item, .ql-mention-list-item:has-text("${mentionedUser.responseData.displayName}")` ); + await mentionItem + .first() + .waitFor({ state: 'visible', timeout: 2000 }) + .catch(() => undefined); if (await mentionItem.isVisible()) { await mentionItem.click(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProductAndSubdomains.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProductAndSubdomains.spec.ts index d93ef785381f..cf4a15f24a11 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProductAndSubdomains.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProductAndSubdomains.spec.ts @@ -36,6 +36,7 @@ import { selectDomain, } from '../../utils/domain'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; +import { waitForSearchIndexed } from '../../utils/polling'; import { sidebarClick } from '../../utils/sidebar'; test.use({ storageState: 'playwright/.auth/admin.json' }); @@ -271,8 +272,12 @@ test.describe('Data Product Comprehensive Tests', () => { } if (retry < maxRetries - 1) { - // eslint-disable-next-line playwright/no-wait-for-timeout -- wait for ES indexing before retry - await page.waitForTimeout(2000); + await waitForSearchIndexed( + apiContext, + user.getUserName(), + 'user_search_index', + { timeout: 3000 } + ).catch(() => undefined); } } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DomainUIInteractions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DomainUIInteractions.spec.ts index 8aa68e01dea8..d5a20867d570 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DomainUIInteractions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DomainUIInteractions.spec.ts @@ -27,6 +27,7 @@ import { selectDomain, } from '../../utils/domain'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; +import { waitForSearchIndexed } from '../../utils/polling'; import { sidebarClick } from '../../utils/sidebar'; const test = base.extend<{ @@ -93,8 +94,12 @@ test.describe('Domain Owner Management', () => { } if (retry < maxRetries - 1) { - // eslint-disable-next-line playwright/no-wait-for-timeout -- wait for ES indexing before retry - await page.waitForTimeout(2000); + await waitForSearchIndexed( + apiContext, + user.getUserName(), + 'user_search_index', + { timeout: 3000 } + ).catch(() => undefined); } } @@ -244,8 +249,12 @@ test.describe('Domain Expert Management', () => { // Wait before retry (ES indexing delay) if (retry < maxRetries - 1) { - // eslint-disable-next-line playwright/no-wait-for-timeout -- wait for ES indexing before retry - await page.waitForTimeout(2000); + await waitForSearchIndexed( + apiContext, + user.getUserName(), + 'user_search_index', + { timeout: 3000 } + ).catch(() => undefined); } } @@ -431,8 +440,12 @@ test.describe('Data Product UI Operations', () => { } if (retry < maxRetries - 1) { - // eslint-disable-next-line playwright/no-wait-for-timeout -- wait for ES indexing before retry - await page.waitForTimeout(2000); + await waitForSearchIndexed( + apiContext, + user.getUserName(), + 'user_search_index', + { timeout: 3000 } + ).catch(() => undefined); } } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ExploreBrowse.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ExploreBrowse.spec.ts index 2daf943e4e30..27daf87fb940 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ExploreBrowse.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ExploreBrowse.spec.ts @@ -225,9 +225,13 @@ test.describe( await expandServiceInExploreTree(page, table.serviceResponseData.name); await test.step('Selecting a service in the tree adds browse chips', async () => { + const browseRes = page.waitForResponse( + '/api/v1/search/query?*index=dataAsset*' + ); await page .getByTestId(`explore-tree-title-${table.serviceResponseData.name}`) .click(); + await browseRes; await waitForAllLoadersToDisappear(page); await expect(page.getByTestId('browse-chip-serviceType')).toBeVisible(); @@ -256,12 +260,22 @@ test.describe( await test.step('Selecting a database service type narrows the browse tree directionally', async () => { await expandTreeNode(page, 'Databases'); - await page - .getByTestId( - `explore-tree-title-${table.service.serviceType.toLowerCase()}` - ) - .click(); + // Explicit visibility wait before clicking. The expandTreeNode helper + // only waits for loaders to disappear, but the tree's child rows can + // continue to animate/reposition for a beat after that — the + // subsequent .click() then times out with "waiting for element to be + // visible, enabled and stable". toBeVisible polls until the element + // is stable too, which lets the click land cleanly. + const serviceTitle = page.getByTestId( + `explore-tree-title-${table.service.serviceType.toLowerCase()}` + ); + await expect(serviceTitle).toBeVisible(); + const browseRes = page.waitForResponse( + '/api/v1/search/query?*index=dataAsset*' + ); + await serviceTitle.click(); + await browseRes; await waitForAllLoadersToDisappear(page); await expect(page.getByTestId('browse-chip-serviceType')).toBeVisible(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskComments.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskComments.spec.ts index 6a55a4d016de..44e6e1868f3c 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskComments.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskComments.spec.ts @@ -528,10 +528,6 @@ test.describe('Task Comments - UI Tests', () => { .click(); await taskFeeds; - // Wait for task cards to load - await page.waitForTimeout(1000); - - // Look for the task card - could be task-feed-card or feed-card-v2 const taskCard = page .locator('[data-testid="task-feed-card"], .task-feed-card-v1-new') .first(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts index 47062aeeabb4..cd662f61dc73 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts @@ -458,7 +458,6 @@ test.describe('Task Activity Feed Integration', () => { await test.step('Resolve task and verify it moves to Closed', async () => { await resolveTaskWithApproval(page); - await page.waitForTimeout(1000); await page.reload(); await waitForPageLoaded(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/polling.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/polling.ts index be97d816e1bd..4e695b7cd492 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/polling.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/polling.ts @@ -19,10 +19,20 @@ import { waitForAllLoadersToDisappear } from './entity'; */ export const waitForSearchIndexed = async ( apiContext: APIRequestContext, - entityFqn: string, + entityFqn: string | undefined, index: string, options?: { timeout?: number; intervals?: number[] } ) => { + // An empty q= becomes a match-all query in the search API: hits.total>0 + // would resolve on the first poll against any non-empty index, silently + // bypassing the very race this helper exists to close. Fail fast with a + // clear message so a missing FQN is debuggable at the source. + if (!entityFqn) { + throw new Error( + `waitForSearchIndexed called with empty FQN for index "${index}"` + ); + } + const timeout = options?.timeout ?? 30_000; const intervals = options?.intervals ?? [500, 1_000, 2_000, 5_000]; const start = Date.now(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts index 2b767aa2ca64..a8ef72ffe84a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts @@ -60,7 +60,12 @@ export const exploreShouldShowEntity = async ( if (shouldSee) { await expect(resultCard.first()).toBeVisible(); } else { - await expect(resultCard).toHaveCount(0); + // RBAC enforcement against newly-assigned user roles lags the patch + // call by several seconds — the search-index user doc needs to update + // before queries get filtered against the role's policies. Use a + // longer timeout on the negative assertion so the result-card has + // time to drop out as the role takes effect. + await expect(resultCard).toHaveCount(0, { timeout: 45_000 }); } }; @@ -82,16 +87,22 @@ export const exploreTreeCategories = async ( await exploreRes; await waitForAllLoadersToDisappear(page); - for (const category of visible) { - await expect( - page.getByTestId(`explore-tree-title-${category}`) - ).toBeVisible(); - } - for (const category of hidden) { - await expect( - page.getByTestId(`explore-tree-title-${category}`) - ).toHaveCount(0); - } + // The explore tree fires multiple aggregation queries to compute per-category + // counts; the tree DOM updates asynchronously as those resolve. Poll the + // category visibility/absence assertions to ride out renders that haven't + // settled yet, rather than relying on a single search-query wait. + await expect(async () => { + for (const category of visible) { + await expect( + page.getByTestId(`explore-tree-title-${category}`) + ).toBeVisible({ timeout: 2_000 }); + } + for (const category of hidden) { + await expect( + page.getByTestId(`explore-tree-title-${category}`) + ).toHaveCount(0, { timeout: 2_000 }); + } + }).toPass({ timeout: 20_000, intervals: [500, 1_000, 2_000] }); }; export const enableDisableSearchRBAC = async ( @@ -135,9 +146,14 @@ export const searchForEntityShouldWork = async ( await page.getByTestId('searchBox').click(); await page.getByTestId('searchBox').fill(fqn); - await page.getByTestId('searchBox').press('Enter'); - await page.waitForResponse(`api/v1/search/query?**`); + // Register waitForResponse BEFORE the action that triggers it. Registering + // after `press('Enter')` is racy — the search response can return before + // the listener is attached, leaving the assertion to run against stale UI + // state. + const searchResponse = page.waitForResponse(`api/v1/search/query?**`); + await page.getByTestId('searchBox').press('Enter'); + await searchResponse; await waitForAllLoadersToDisappear(page); @@ -169,9 +185,12 @@ export const searchForEntityShouldWorkShowNoResult = async ( await page.getByTestId('searchBox').click(); await page.getByTestId('searchBox').fill(fqn); - await page.getByTestId('searchBox').press('Enter'); - await page.waitForResponse(`api/v1/search/query?**`); + // Register waitForResponse BEFORE the action that triggers it. See the + // matching note in searchForEntityShouldWork for why. + const searchResponse = page.waitForResponse(`api/v1/search/query?**`); + await page.getByTestId('searchBox').press('Enter'); + await searchResponse; await waitForAllLoadersToDisappear(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts index 5f7709bef516..0cb237bfba82 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts @@ -384,7 +384,11 @@ export const getTaskCard = (page: Page, task: CreatedTask) => { export const openTaskDetails = async (page: Page, task: CreatedTask) => { const taskCard = getTaskCard(page, task); logTaskDebug('openTaskDetails:waitingForCard', task.taskId); - await expect(taskCard).toBeVisible({ timeout: 15000 }); + // The activity-feed UI re-fetches its list after the task is created via + // API; under Basic-project parallelism the refresh can lag past 15s and + // the card never appears within the default timeout. 45s gives the feed + // enough time to propagate without slowing healthy runs. + await expect(taskCard).toBeVisible({ timeout: 45000 }); logTaskDebug('openTaskDetails:click', task.taskId); await taskCard.click(); await expect(page.locator(TASK_TAB_SELECTOR)).toBeVisible();