Skip to content

playwright(fix): test reliability — ES indexing, locator strict-mode, waitForResponse races#29585

Open
chirag-madlani wants to merge 9 commits into
mainfrom
playwright-test-reliability-fixes
Open

playwright(fix): test reliability — ES indexing, locator strict-mode, waitForResponse races#29585
chirag-madlani wants to merge 9 commits into
mainfrom
playwright-test-reliability-fixes

Conversation

@chirag-madlani

@chirag-madlani chirag-madlani commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes:

PR-A of a 4-way split of #29435. This PR contains only the test-side bug fixes — no config changes, no spec-file moves, no sharding shift. Each fix addresses a real race or locator issue in an existing Playwright E2E spec that was previously surviving on retries:2 but became visible under retry pressure.

The fixes fall into 4 categories:

ES indexing races — entity created via API, then read by UI before search index updates:

  • BulkEditEntity.spec.ts: wait for glossary_term_search_index
  • BulkEditOperationBadges.spec.ts: same pattern in beforeAll
  • BulkImport.spec.ts: wait for database_service / database_schema / table indexes; pre-wait .rdg-cell-details count before asserting text
  • TestCaseImportExportE2eFlow.spec.ts: wait for test_case_search_index so the just-created test case is in the export CSV

waitForResponse races — listener registered after the action, or matching a too-broad URL that resolves on a stale earlier response:

  • utils/searchRBAC.ts searchForEntity helpers: register before press('Enter')
  • SearchIndexNestedColumns.spec.ts: predicate-match the URL containing the encoded search term

Locator stability / timeout fixes:

  • Roles.spec.ts (×7 sites): replace strict-mode-violating expect(loader).not.toBeVisible() with waitForAllLoadersToDisappear (handles N loaders correctly)
  • ExploreBrowse.spec.ts: explicit toBeVisible before clicking tree title so the tree settles after expandTreeNode
  • Table.spec.ts: wait for sort + next-page responses, replace count()===15 with toHaveCount(15) auto-retry

Backend-propagation timeouts — UI fetches lag the API write:

  • utils/searchRBAC.ts exploreTreeCategories: wrap visibility/absence checks in expect.toPass({ timeout: 20s }) — tree counts come from multiple aggregation queries
  • utils/searchRBAC.ts exploreShouldShowEntity: bump negative-assertion to 45s — RBAC filtering against newly-assigned roles lags the patch
  • utils/taskWorkflow.ts openTaskDetails: 15s → 45s — activity-feed refresh after API task create lags past default under load

Also removed 7 unannotated waitForTimeout calls leaking past ESLint, replaced with proper waits. The 4 in DomainUIInteractions / DataProductAndSubdomains migrated to the existing waitForSearchIndexed polling helper.

Type of change:

  • Bug fix (test-side)

High-level design:

Each fix is independent and self-contained. No shared helpers introduced; just makes existing tests' assertions deterministic against the system they're already testing. The PR is a clean 16-file diff (+214 / -49) with no file moves, deletes, or new tests.

Tests:

Pure test-side fixes. No new tests needed — each change makes an existing flaky assertion deterministic. Verified locally that prettier + organize-imports pass and that tsc --noEmit introduces no new errors (pre-existing TS errors on main are unaffected).

Playwright (UI) tests

  • Not applicable for adding tests — existing tests are made more robust.

UI screen recording / screenshots:

Not applicable — no UI changes.

Checklist:

  • I have read the CONTRIBUTING document.
  • I have commented on my code, particularly in hard-to-understand areas.

Split off from #29435 — this is the first of four progressively-merging PRs (test fixes → single-spec caps → config + dedup → PR/stress project split). No associated GitHub issue — this is internal test-tooling work.

🤖 Generated with Claude Code

Greptile Summary

This PR addresses several categories of Playwright E2E test flakiness: ES indexing races (entities created via API are read by the UI before search indexes update), waitForResponse listener ordering issues, locator strict-mode violations, and backend-propagation timeouts.

  • ES indexing waits: waitForSearchIndexed calls added in beforeAll/beforeEach blocks for BulkEditEntity, BulkEditOperationBadges, BulkImport, and TestCaseImportExportE2eFlow; polling.ts gains a guard that throws on an empty FQN to prevent silent match-all queries.
  • waitForResponse fixes: searchForEntityShouldWork and searchForEntityShouldWorkShowNoResult now register the listener after fill and before press('Enter'); SearchIndexNestedColumns narrows the predicate to the encoded search term; ExploreBrowse wraps tree-node clicks with targeted response waits.
  • Timeout/locator hardening: openTaskDetails timeout bumped to 45 s, exploreTreeCategories wrapped in expect.toPass, Table.spec.ts uses toHaveCount instead of synchronous count(), and seven waitForTimeout calls replaced with proper event-driven waits.

Confidence Score: 4/5

Safe to merge with one known remaining race in exploreShouldShowEntity that was explicitly fixed elsewhere in this same PR but not at this call site.

The PR correctly fixes the waitForResponse ordering in searchForEntityShouldWork and searchForEntityShouldWorkShowNoResult, but exploreShouldShowEntity on line 48 still registers its broad listener before fill(), meaning an autocomplete response triggered by typing can resolve the listener prematurely and let the resultCard assertion run against stale UI. This is the exact race the PR set out to close, and it remains open in this function.

openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts — the exploreShouldShowEntity function still has the waitForResponse listener registered before fill().

Important Files Changed

Filename Overview
openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts Fixed waitForResponse ordering in searchForEntityShouldWork and searchForEntityShouldWorkShowNoResult, added toPass polling for exploreTreeCategories, and bumped negative-assertion timeout in exploreShouldShowEntity — but exploreShouldShowEntity still registers its waitForResponse listener before fill(), leaving the autocomplete-resolution race unfixed.
openmetadata-ui/src/main/resources/ui/playwright/utils/polling.ts Added a guard that throws when entityFqn is falsy, preventing a silent match-all ES query from masking a missing FQN; also widened the type signature to string
openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts Bumped openTaskDetails visibility timeout from 15 s to 45 s to accommodate activity-feed refresh lag under parallel test load; change is well-justified in comments.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts Added ES-indexing waits for database service, database schema, and table before their respective bulk-import flows; also pre-waits for rdg-cell-details count before asserting text to prevent mid-render failures.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchIndexNestedColumns.spec.ts Replaced broad waitForResponse listener for by-name search with a direct DOM element wait; narrowed by-column waitForResponse to a predicate matching the encoded leaf column name to avoid early resolution on unrelated autocomplete responses.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Table.spec.ts Added waitForResponse listeners for sort and next-page API calls, replaced synchronous count() === 15 with toHaveCount(15) for auto-retry against delayed table re-renders.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ExploreBrowse.spec.ts Added explicit toBeVisible wait before clicking the service-type tree node to let animations settle post-expand; wrapped clicks with waitForResponse listeners for the browse query.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DomainUIInteractions.spec.ts Replaced three waitForTimeout(2000) calls in retry loops with waitForSearchIndexed calls on user_search_index (timeout: 3000, errors swallowed), making the wait semantically meaningful within the retry context.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProductAndSubdomains.spec.ts Same waitForTimeout → waitForSearchIndexed migration as DomainUIInteractions.spec.ts; change is correct and consistent.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts Added waitForSearchIndexed for glossary_term_search_index before bulk-edit opens; comment clearly explains the 'Entity created vs updated' failure mode this prevents.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditOperationBadges.spec.ts Added waitForSearchIndexed for both glossary_term_search_index and table_search_index in beforeAll to prevent empty bulk-edit grids due to ES indexing lag.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts Added waitForSearchIndexed on test_case_search_index in both Admin and non-Admin beforeAll blocks so the export CSV includes the just-created test case.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityStream.spec.ts Removed two waitForTimeout(2000) calls; replaced one with a waitFor({ state: 'visible', timeout: 2000 }).catch(() => undefined) for a conditional-visibility panel.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskComments.spec.ts Replaced two waitForTimeout calls with waitFor({ state: 'visible', timeout: 2000 }).catch(() => undefined) for mention dropdown and mention item.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskComments.spec.ts Removed waitForTimeout(1000) before task card lookup; relies on subsequent locator interactions to self-synchronize.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts Removed waitForTimeout(1000) before page.reload(); the subsequent waitForPageLoaded call is the proper synchronization point.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant T as Test
    participant P as Playwright Page
    participant API as Search API
    participant ES as Elasticsearch

    Note over T,ES: Fixed pattern (searchForEntityShouldWork)
    T->>P: fill(fqn)
    P-->>API: autocomplete query (may fire)
    T->>P: register waitForResponse BEFORE press
    T->>P: press('Enter')
    P-->>API: search results query
    API-->>ES: query index
    ES-->>API: hits
    API-->>P: response
    P-->>T: waitForResponse resolves correctly

    Note over T,ES: Remaining race (exploreShouldShowEntity - NOT fixed)
    T->>P: register waitForResponse (line 48)
    T->>P: fill(fqn)
    P-->>API: autocomplete query fires
    API-->>P: autocomplete response resolves listener prematurely
    T->>P: press('Enter')
    P-->>API: actual search results query
    T->>P: assertion runs against stale UI
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant T as Test
    participant P as Playwright Page
    participant API as Search API
    participant ES as Elasticsearch

    Note over T,ES: Fixed pattern (searchForEntityShouldWork)
    T->>P: fill(fqn)
    P-->>API: autocomplete query (may fire)
    T->>P: register waitForResponse BEFORE press
    T->>P: press('Enter')
    P-->>API: search results query
    API-->>ES: query index
    ES-->>API: hits
    API-->>P: response
    P-->>T: waitForResponse resolves correctly

    Note over T,ES: Remaining race (exploreShouldShowEntity - NOT fixed)
    T->>P: register waitForResponse (line 48)
    T->>P: fill(fqn)
    P-->>API: autocomplete query fires
    API-->>P: autocomplete response resolves listener prematurely
    T->>P: press('Enter')
    P-->>API: actual search results query
    T->>P: assertion runs against stale UI
Loading

Comments Outside Diff (5)

  1. openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts, line 48-51 (link)

    P2 waitForResponse still registered before fill() in exploreShouldShowEntity

    searchForEntityShouldWork and searchForEntityShouldWorkShowNoResult were fixed in this PR to register the listener between fill and press('Enter') specifically because fill can trigger an autocomplete/suggestion query that resolves the broad waitForResponse('/api/v1/search/query?*') listener prematurely. exploreShouldShowEntity still registers searchRes before fill() on line 48, leaving it exposed to the same race: if the search box fires a suggestion fetch while the user is typing, searchRes resolves on that response and press('Enter')'s actual result page is never waited for, so the resultCard assertion can run against stale UI.

  2. openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts, line 48-51 (link)

    P1 waitForResponse still registered before fill() in exploreShouldShowEntity

    searchRes is registered before fill(fqn) on line 48. If the search box fires an autocomplete/suggestion query while the user is typing, searchRes resolves on that early response and the press('Enter') result page is never awaited — so the resultCard assertion runs against stale UI. This is the exact race that was deliberately fixed in searchForEntityShouldWork (register after fill, before press) but the same pattern was left unremedied here.

  3. openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts, line 48-51 (link)

    P1 searchRes is registered before fill(fqn) (line 49), so if the search box fires an autocomplete/suggestion query while the user types, that early response resolves the listener and the press('Enter') result-page response is never awaited — the resultCard assertion then runs against stale UI. This is exactly the race fixed in searchForEntityShouldWork and searchForEntityShouldWorkShowNoResult in this same PR: register the listener after fill, before press.

  4. openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts, line 48-51 (link)

    P1 searchRes is registered before fill(fqn) on line 48. The fill() call on line 49 can immediately trigger an autocomplete/suggestion query whose URL matches '/api/v1/search/query?*', resolving the listener before press('Enter') fires the actual results-page query. The assertion then runs against stale autocomplete results rather than the real search page. This is the exact race that was deliberately fixed in searchForEntityShouldWork and searchForEntityShouldWorkShowNoResult in this same PR — register after fill, before press.

  5. openmetadata-ui/src/main/resources/ui/playwright/utils/searchRBAC.ts, line 48-51 (link)

    P1 waitForResponse registered before fill() — same race left unfixed

    searchRes (line 48) is created before fill(fqn) executes on line 49. Typing into the search box triggers autocomplete queries whose URL matches '/api/v1/search/query?*', which can resolve searchRes on that autocomplete response before press('Enter') fires the actual search-page query. When that happens the waitForAllLoadersToDisappear and the resultCard assertion run against the autocomplete-populated UI, not the search-results page — so toHaveCount(0, { timeout: 45_000 }) for the negative path may pass or fail spuriously.

    searchForEntityShouldWork and searchForEntityShouldWorkShowNoResult were fixed in this very PR to move the listener registration to after fill and before press('Enter') for exactly this reason. The same fix is needed here.

Reviews (7): Last reviewed commit: "playwright(fix): guard waitForSearchInde..." | Re-trigger Greptile

… waitForResponse races

Bundle of test-side bug fixes for races and locator issues in existing
Playwright E2E specs. No config, no test-file moves, no behavior change
to the system under test — just makes flaky assertions deterministic.

ES indexing races (entities created via API, then read by UI before
search index updates):
- BulkEditEntity.spec.ts: wait for glossary_term_search_index before
  opening bulk-edit table (otherwise the term shows as "Entity created"
  instead of "Entity updated")
- BulkEditOperationBadges.spec.ts: same pattern for the file's beforeAll
  (term + table search indexes)
- BulkImport.spec.ts: wait for database_service / database_schema /
  table search indexes; pre-wait .rdg-cell-details rendering before
  asserting text (was racing the async grid populate)
- TestCaseImportExportE2eFlow.spec.ts: wait for test_case_search_index
  so the just-created test case appears in the export's CSV

waitForResponse races (listener registered after the action, or matching
too-broad URL pattern that resolves on a stale earlier response):
- utils/searchRBAC.ts searchForEntityShouldWork[ShowNoResult]: register
  before press('Enter')
- SearchIndexNestedColumns.spec.ts: predicate-match the URL that
  contains the encoded search term (the empty-query suggestion fetch
  was resolving the listener first)

Locator-stability / timeout fixes:
- Roles.spec.ts (×7 sites): replace strict-mode-violating
  expect(loader).not.toBeVisible() with waitForAllLoadersToDisappear
  (handles N loaders correctly)
- ExploreBrowse.spec.ts: explicit toBeVisible before clicking
  explore-tree-title-mysql so the tree settles after expandTreeNode
- Table.spec.ts: wait for sort + next-page responses, replace
  count()===15 with toHaveCount(15) auto-retry

Backend-propagation timeouts (the UI fetches lag the API write):
- utils/searchRBAC.ts exploreTreeCategories: wrap visibility/absence
  checks in expect.toPass({ timeout: 20s }) — tree counts come from
  multiple aggregation queries, single search-query wait is not enough
- utils/searchRBAC.ts exploreShouldShowEntity: bump negative-assertion
  to 45s — RBAC filtering against newly-assigned roles lags the patch
  by several seconds
- utils/taskWorkflow.ts openTaskDetails: 15s → 45s — activity-feed
  refresh after API task create lags past default under load

Removed 7 unannotated `waitForTimeout` calls that were leaking past
ESLint and replaced with proper waits where applicable (Tasks/, Pages/
TaskComments + TasksUIFlow, ActivityStream, ActivityFeed). The 4 in
DomainUIInteractions / DataProductAndSubdomains were migrated to the
existing `waitForSearchIndexed` polling helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chirag-madlani chirag-madlani requested a review from a team as a code owner June 29, 2026 16:01
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs UI UI specific issues labels Jun 29, 2026
Comment thread openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts Outdated
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (9 flaky)

✅ 807 passed · ❌ 0 failed · 🟡 9 flaky · ⏭️ 9 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 2 807 0 9 9
🟡 9 flaky test(s) (passed on retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/BulkImport.spec.ts › Database service (shard 2, 1 retry)
  • Features/BulkImport.spec.ts › Database Schema (shard 2, 1 retry)
  • Features/Container.spec.ts › Container page children pagination (shard 2, 1 retry)
  • Features/Container.spec.ts › Copy column link button should copy the column URL to clipboard (shard 2, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with view-only permission cannot see restore or delete actions on an archived document (shard 2, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with editAll permission can see restore action but not delete action on an archived document, and can restore it (shard 2, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with deleteAll permission can see delete action but not restore action on an archived document, and can delete it (shard 2, 2 retries)
  • Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts › Admin: Complete export-import-validate flow (shard 2, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

karanh37
karanh37 previously approved these changes Jun 30, 2026
@gitar-bot

gitar-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 1 resolved / 4 findings

Stabilizes flaky Playwright E2E tests by addressing ES indexing race conditions, correcting waitForResponse listener ordering, and fixing locator strict-mode violations. Please resolve the remaining race condition in exploreShouldShowEntity within searchRBAC.ts to ensure consistent test execution.

💡 Edge Case: Empty-FQN fallback silently no-ops waitForSearchIndexed

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts:808 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts:79 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts:150

Several new call sites pass a ?? '' fallback as the entity FQN to waitForSearchIndexed, e.g. tableEntity.entityResponseData.fullyQualifiedName ?? '' (BulkImport.spec.ts:808) and table.testCasesResponseData[0]?.fullyQualifiedName ?? '' (TestCaseImportExportE2eFlow.spec.ts:79,150).

waitForSearchIndexed builds the request as /api/v1/search/query?q=${encodeURIComponent(entityFqn)}&index=...&size=1 and returns as soon as hits.total.value > 0 (polling.ts:31-44). When entityFqn is the empty string, q= becomes a match-all query, so the very first poll returns hits as long as the index has any document — the function returns immediately without ever confirming the just-created entity is indexed. That silently defeats the exact race these waits are meant to close, and the test then proceeds to the flaky assertion the PR was trying to stabilize (export missing the new test case / empty bulk-edit grid), producing a confusing failure far from the root cause rather than a clear timeout here.

This only triggers if the FQN is actually undefined, but in that case failing fast with a clear message is far more debuggable than a silent bypass. Consider asserting the FQN is present (or have waitForSearchIndexed throw on an empty query) instead of substituting ''.

Assert the FQN is present before waiting, so a missing FQN fails fast with a clear message instead of a match-all no-op.
const tableFqn = tableEntity.entityResponseData.fullyQualifiedName;
expect(tableFqn, 'table FQN must exist before indexing wait').toBeTruthy();
await waitForSearchIndexed(apiContext, tableFqn, 'table_search_index');
Make the helper itself reject an empty query so the bypass can never happen silently.
// in polling.ts, guard against an empty query that would match all docs
if (!entityFqn) {
  throw new Error(`waitForSearchIndexed called with empty FQN for index "${index}"`);
}
💡 Quality: Closed-tab verification can silently no-op after reload

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts:458-472

The 'Resolve task and verify it moves to Closed' step previously called navigateToActivityFeedTasks(page) after page.reload(), guaranteeing the test landed on the Tasks/Activity-feed view before asserting. This commit replaces that navigation with only waitForPageLoaded(page). The subsequent verification is wrapped in if (await closedTab.isVisible()) { ... }. If, after reload, the page no longer renders the Tasks view (e.g. the URL/route does not restore the tab), closedTab will not be visible and the entire verification block — including the expect(closedTaskCard).toBeVisible() assertion — is skipped, so the step passes without verifying the task actually moved to Closed. This turns a meaningful assertion into a potential no-op. Consider re-asserting that the Tasks view (or Closed tab) is present after reload, or removing the conditional guard so the assertion is enforced.

Re-navigate to the tasks tab after reload and assert the Closed tab is present rather than conditionally skipping verification.
await page.reload();
await waitForPageLoaded(page);
await navigateToActivityFeedTasks(page);

const closedTab = page.getByRole('tab', { name: /Closed/i });
await expect(closedTab).toBeVisible();
await closedTab.click();
💡 Edge Case: openEntityTasksTab drops the button-variant Tasks tab fallback

📄 openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts:357-371

The refactor of openEntityTasksTab removes the previous branch that handled a button-role Tasks tab (page.getByRole('button', { name: /tasks/i })) as an alternative to the menuitem-role tab, and also removes the isVisible() guards. The function now unconditionally does menuItemTaskTab.waitFor({ state: 'visible' }) and clicks it. If any entity page renders the Tasks tab as a button rather than a menuitem (which the old code explicitly accounted for), waitFor will hang until timeout and fail the test. If the menuitem variant is now the only one rendered across all entity types this is safe; otherwise this is a regression in robustness. Confirm the button variant no longer exists in the UI, or retain the fallback.

Preserve support for both the menuitem and button Tasks-tab variants.
const menuItemTaskTab = page.getByRole('menuitem', { name: /tasks/i });
const buttonTaskTab = page.getByRole('button', { name: /tasks/i });
const taskTab = (await menuItemTaskTab.isVisible().catch(() => false))
  ? menuItemTaskTab
  : buttonTaskTab;
await taskTab.waitFor({ state: 'visible' });
const taskListResponse = waitForTaskListResponse(page);
await taskTab.click();
await taskListResponse.catch(() => undefined);
✅ 1 resolved
Quality: Unused escape import added to SearchIndexNestedColumns.spec.ts

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchIndexNestedColumns.spec.ts:17
This commit adds import { escape } from 'lodash'; (line 17), but escape is never referenced anywhere in the file — a Grep for escape returns only the import itself. The previous code in this commit removed the byNameResponse waitForResponse logic that presumably would have used it, but the import was left behind.

This is dead code and will be flagged by the project's ESLint (no-unused-vars/@typescript-eslint/no-unused-vars), which the review instructions require running (eslint --fix). It could cause a lint/CI failure or at minimum be auto-stripped by organize-imports:cli. Remove the unused import.

🤖 Prompt for agents
Code Review: Stabilizes flaky Playwright E2E tests by addressing ES indexing race conditions, correcting `waitForResponse` listener ordering, and fixing locator strict-mode violations. Please resolve the remaining race condition in `exploreShouldShowEntity` within `searchRBAC.ts` to ensure consistent test execution.

1. 💡 Edge Case: Empty-FQN fallback silently no-ops waitForSearchIndexed
   Files: openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts:808, openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts:79, openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts:150

   Several new call sites pass a `?? ''` fallback as the entity FQN to `waitForSearchIndexed`, e.g. `tableEntity.entityResponseData.fullyQualifiedName ?? ''` (BulkImport.spec.ts:808) and `table.testCasesResponseData[0]?.fullyQualifiedName ?? ''` (TestCaseImportExportE2eFlow.spec.ts:79,150).
   
   `waitForSearchIndexed` builds the request as `/api/v1/search/query?q=${encodeURIComponent(entityFqn)}&index=...&size=1` and returns as soon as `hits.total.value > 0` (polling.ts:31-44). When `entityFqn` is the empty string, `q=` becomes a match-all query, so the very first poll returns hits as long as the index has any document — the function returns immediately without ever confirming the just-created entity is indexed. That silently defeats the exact race these waits are meant to close, and the test then proceeds to the flaky assertion the PR was trying to stabilize (export missing the new test case / empty bulk-edit grid), producing a confusing failure far from the root cause rather than a clear timeout here.
   
   This only triggers if the FQN is actually undefined, but in that case failing fast with a clear message is far more debuggable than a silent bypass. Consider asserting the FQN is present (or have `waitForSearchIndexed` throw on an empty query) instead of substituting `''`.

   Fix (Assert the FQN is present before waiting, so a missing FQN fails fast with a clear message instead of a match-all no-op.):
   const tableFqn = tableEntity.entityResponseData.fullyQualifiedName;
   expect(tableFqn, 'table FQN must exist before indexing wait').toBeTruthy();
   await waitForSearchIndexed(apiContext, tableFqn, 'table_search_index');

   Fix (Make the helper itself reject an empty query so the bypass can never happen silently.):
   // in polling.ts, guard against an empty query that would match all docs
   if (!entityFqn) {
     throw new Error(`waitForSearchIndexed called with empty FQN for index "${index}"`);
   }

2. 💡 Quality: Closed-tab verification can silently no-op after reload
   Files: openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts:458-472

   The 'Resolve task and verify it moves to Closed' step previously called `navigateToActivityFeedTasks(page)` after `page.reload()`, guaranteeing the test landed on the Tasks/Activity-feed view before asserting. This commit replaces that navigation with only `waitForPageLoaded(page)`. The subsequent verification is wrapped in `if (await closedTab.isVisible()) { ... }`. If, after reload, the page no longer renders the Tasks view (e.g. the URL/route does not restore the tab), `closedTab` will not be visible and the entire verification block — including the `expect(closedTaskCard).toBeVisible()` assertion — is skipped, so the step passes without verifying the task actually moved to Closed. This turns a meaningful assertion into a potential no-op. Consider re-asserting that the Tasks view (or Closed tab) is present after reload, or removing the conditional guard so the assertion is enforced.

   Fix (Re-navigate to the tasks tab after reload and assert the Closed tab is present rather than conditionally skipping verification.):
   await page.reload();
   await waitForPageLoaded(page);
   await navigateToActivityFeedTasks(page);
   
   const closedTab = page.getByRole('tab', { name: /Closed/i });
   await expect(closedTab).toBeVisible();
   await closedTab.click();

3. 💡 Edge Case: openEntityTasksTab drops the button-variant Tasks tab fallback
   Files: openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts:357-371

   The refactor of `openEntityTasksTab` removes the previous branch that handled a `button`-role Tasks tab (`page.getByRole('button', { name: /tasks/i })`) as an alternative to the `menuitem`-role tab, and also removes the `isVisible()` guards. The function now unconditionally does `menuItemTaskTab.waitFor({ state: 'visible' })` and clicks it. If any entity page renders the Tasks tab as a button rather than a menuitem (which the old code explicitly accounted for), `waitFor` will hang until timeout and fail the test. If the menuitem variant is now the only one rendered across all entity types this is safe; otherwise this is a regression in robustness. Confirm the button variant no longer exists in the UI, or retain the fallback.

   Fix (Preserve support for both the menuitem and button Tasks-tab variants.):
   const menuItemTaskTab = page.getByRole('menuitem', { name: /tasks/i });
   const buttonTaskTab = page.getByRole('button', { name: /tasks/i });
   const taskTab = (await menuItemTaskTab.isVisible().catch(() => false))
     ? menuItemTaskTab
     : buttonTaskTab;
   await taskTab.waitFor({ state: 'visible' });
   const taskListResponse = waitForTaskListResponse(page);
   await taskTab.click();
   await taskListResponse.catch(() => undefined);

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

chirag-madlani and others added 2 commits June 30, 2026 19:44
An empty FQN encoded into the q= parameter becomes a match-all query,
which lets the helper return on the first poll against any non-empty
index — silently bypassing the very indexing race it exists to close.
Throw a clear error at the source instead, and drop the misleading
`?? ''` fallback at call sites so the type contract reflects reality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown
Code Review 👍 Approved with suggestions 2 resolved / 4 findings

Hardens E2E tests against ES indexing races and locator timeouts by implementing robust polling and deterministic response handling. Address the remaining waitForResponse race in exploreShouldShowEntity where the listener is registered prematurely before input completion.

💡 Quality: Closed-tab verification can silently no-op after reload

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts:458-472

The 'Resolve task and verify it moves to Closed' step previously called navigateToActivityFeedTasks(page) after page.reload(), guaranteeing the test landed on the Tasks/Activity-feed view before asserting. This commit replaces that navigation with only waitForPageLoaded(page). The subsequent verification is wrapped in if (await closedTab.isVisible()) { ... }. If, after reload, the page no longer renders the Tasks view (e.g. the URL/route does not restore the tab), closedTab will not be visible and the entire verification block — including the expect(closedTaskCard).toBeVisible() assertion — is skipped, so the step passes without verifying the task actually moved to Closed. This turns a meaningful assertion into a potential no-op. Consider re-asserting that the Tasks view (or Closed tab) is present after reload, or removing the conditional guard so the assertion is enforced.

Re-navigate to the tasks tab after reload and assert the Closed tab is present rather than conditionally skipping verification.
await page.reload();
await waitForPageLoaded(page);
await navigateToActivityFeedTasks(page);

const closedTab = page.getByRole('tab', { name: /Closed/i });
await expect(closedTab).toBeVisible();
await closedTab.click();
💡 Edge Case: openEntityTasksTab drops the button-variant Tasks tab fallback

📄 openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts:357-371

The refactor of openEntityTasksTab removes the previous branch that handled a button-role Tasks tab (page.getByRole('button', { name: /tasks/i })) as an alternative to the menuitem-role tab, and also removes the isVisible() guards. The function now unconditionally does menuItemTaskTab.waitFor({ state: 'visible' }) and clicks it. If any entity page renders the Tasks tab as a button rather than a menuitem (which the old code explicitly accounted for), waitFor will hang until timeout and fail the test. If the menuitem variant is now the only one rendered across all entity types this is safe; otherwise this is a regression in robustness. Confirm the button variant no longer exists in the UI, or retain the fallback.

Preserve support for both the menuitem and button Tasks-tab variants.
const menuItemTaskTab = page.getByRole('menuitem', { name: /tasks/i });
const buttonTaskTab = page.getByRole('button', { name: /tasks/i });
const taskTab = (await menuItemTaskTab.isVisible().catch(() => false))
  ? menuItemTaskTab
  : buttonTaskTab;
await taskTab.waitFor({ state: 'visible' });
const taskListResponse = waitForTaskListResponse(page);
await taskTab.click();
await taskListResponse.catch(() => undefined);
✅ 2 resolved
Quality: Unused escape import added to SearchIndexNestedColumns.spec.ts

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchIndexNestedColumns.spec.ts:17
This commit adds import { escape } from 'lodash'; (line 17), but escape is never referenced anywhere in the file — a Grep for escape returns only the import itself. The previous code in this commit removed the byNameResponse waitForResponse logic that presumably would have used it, but the import was left behind.

This is dead code and will be flagged by the project's ESLint (no-unused-vars/@typescript-eslint/no-unused-vars), which the review instructions require running (eslint --fix). It could cause a lint/CI failure or at minimum be auto-stripped by organize-imports:cli. Remove the unused import.

Edge Case: Empty-FQN fallback silently no-ops waitForSearchIndexed

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkImport.spec.ts:808 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts:79 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts:150
Several new call sites pass a ?? '' fallback as the entity FQN to waitForSearchIndexed, e.g. tableEntity.entityResponseData.fullyQualifiedName ?? '' (BulkImport.spec.ts:808) and table.testCasesResponseData[0]?.fullyQualifiedName ?? '' (TestCaseImportExportE2eFlow.spec.ts:79,150).

waitForSearchIndexed builds the request as /api/v1/search/query?q=${encodeURIComponent(entityFqn)}&index=...&size=1 and returns as soon as hits.total.value > 0 (polling.ts:31-44). When entityFqn is the empty string, q= becomes a match-all query, so the very first poll returns hits as long as the index has any document — the function returns immediately without ever confirming the just-created entity is indexed. That silently defeats the exact race these waits are meant to close, and the test then proceeds to the flaky assertion the PR was trying to stabilize (export missing the new test case / empty bulk-edit grid), producing a confusing failure far from the root cause rather than a clear timeout here.

This only triggers if the FQN is actually undefined, but in that case failing fast with a clear message is far more debuggable than a silent bypass. Consider asserting the FQN is present (or have waitForSearchIndexed throw on an empty query) instead of substituting ''.

🤖 Prompt for agents
Code Review: Hardens E2E tests against ES indexing races and locator timeouts by implementing robust polling and deterministic response handling. Address the remaining `waitForResponse` race in `exploreShouldShowEntity` where the listener is registered prematurely before input completion.

1. 💡 Quality: Closed-tab verification can silently no-op after reload
   Files: openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts:458-472

   The 'Resolve task and verify it moves to Closed' step previously called `navigateToActivityFeedTasks(page)` after `page.reload()`, guaranteeing the test landed on the Tasks/Activity-feed view before asserting. This commit replaces that navigation with only `waitForPageLoaded(page)`. The subsequent verification is wrapped in `if (await closedTab.isVisible()) { ... }`. If, after reload, the page no longer renders the Tasks view (e.g. the URL/route does not restore the tab), `closedTab` will not be visible and the entire verification block — including the `expect(closedTaskCard).toBeVisible()` assertion — is skipped, so the step passes without verifying the task actually moved to Closed. This turns a meaningful assertion into a potential no-op. Consider re-asserting that the Tasks view (or Closed tab) is present after reload, or removing the conditional guard so the assertion is enforced.

   Fix (Re-navigate to the tasks tab after reload and assert the Closed tab is present rather than conditionally skipping verification.):
   await page.reload();
   await waitForPageLoaded(page);
   await navigateToActivityFeedTasks(page);
   
   const closedTab = page.getByRole('tab', { name: /Closed/i });
   await expect(closedTab).toBeVisible();
   await closedTab.click();

2. 💡 Edge Case: openEntityTasksTab drops the button-variant Tasks tab fallback
   Files: openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts:357-371

   The refactor of `openEntityTasksTab` removes the previous branch that handled a `button`-role Tasks tab (`page.getByRole('button', { name: /tasks/i })`) as an alternative to the `menuitem`-role tab, and also removes the `isVisible()` guards. The function now unconditionally does `menuItemTaskTab.waitFor({ state: 'visible' })` and clicks it. If any entity page renders the Tasks tab as a button rather than a menuitem (which the old code explicitly accounted for), `waitFor` will hang until timeout and fail the test. If the menuitem variant is now the only one rendered across all entity types this is safe; otherwise this is a regression in robustness. Confirm the button variant no longer exists in the UI, or retain the fallback.

   Fix (Preserve support for both the menuitem and button Tasks-tab variants.):
   const menuItemTaskTab = page.getByRole('menuitem', { name: /tasks/i });
   const buttonTaskTab = page.getByRole('button', { name: /tasks/i });
   const taskTab = (await menuItemTaskTab.isVisible().catch(() => false))
     ? menuItemTaskTab
     : buttonTaskTab;
   await taskTab.waitFor({ state: 'visible' });
   const taskListResponse = waitForTaskListResponse(page);
   await taskTab.click();
   await taskListResponse.catch(() => undefined);

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants