Skip to content

Fix flaky UserDetails Playwright spec: visitUserProfilePage hangs on an undispatched search - #31647

Merged
harsh-vador merged 3 commits into
mainfrom
fix/flaky-visit-user-profile-page
Aug 19, 2026
Merged

Fix flaky UserDetails Playwright spec: visitUserProfilePage hangs on an undispatched search#31647
harsh-vador merged 3 commits into
mainfrom
fix/flaky-visit-user-profile-page

Conversation

@harsh-vador

@harsh-vador harsh-vador commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Describe your changes:

I worked on the flaky UserDetails.spec.ts test because it fails intermittently on the nightly run (2.0 nightly run 31918043673) with a 49s hang inside visitUserProfilePage, not inside the spec itself.

Failing test: Create team with domain and verify visibility of inherited domain in user profile after team removal

Trace: Wait for event "response" — playwright/utils/user.ts:13949.1s, followed by Timed out waiting for user <name> to become visible in the user list. The failure screenshot shows the user row rendered correctly — nothing was actually missing.

Root cause

visitUserProfilePage retried its user-list search inside an expect.poll by clearing the search box and re-typing the same username:

const searchRequest = page.waitForResponse('/api/v1/search/query*'); // unbounded
await searchBar.fill('');
await searchBar.fill(userName);
await searchRequest;

That retry can never dispatch a second search:

  • SearchBar.component.tsx:131 sets typingInterval: 1000, so fill('') and fill(userName) (~20ms apart) collapse into a single debounced handleSearch(userName).
  • UserListPageV1.tsx:230handleSearch only calls setFilters({ user: value }).
  • UserListPageV1.tsx:247-250 — the fetch effect keys on [searchValue, currentPage, isDeleted]. Re-entering the term already held in searchValue leaves it unchanged, so the effect never re-runs and no request is made.

The first iteration works (empty box → '' → userName is a real change). Every later iteration deadlocks on an unbounded waitForResponse for a request that will never be sent.

Two aggravating factors:

  1. The poll budget was 60000, equal to the test timeout in playwright.config.ts:386. The poll could never exhaust its own retries, so the test died mid-poll and reported the raw wait rather than the poll's diagnostic message.
  2. The sibling test Admin user can edit teams from the user profile calls the same helper but declares test.slow(). This one did not, so it ran on the bare 60s budget.

Why it surfaces on nightly and not locally: CI runs workers: 3, which lengthens ES index lag and makes the helper reach retry 2+ far more often. Locally the first iteration usually succeeds and the latent deadlock never fires.

The fix

Replace the retry loop with a single deterministic search. The search box is empty on arrival, so one fill(userName) is always a real value change and always dispatches:

settingClick → loader detached → arm response → fill → await → loader detached → expect visible → click
  • Response predicate is scoped to the username, so a stray search/query from another component cannot satisfy the wait and mask an unpopulated list.
  • Dropped two dead waits (userResponse, created and never meaningfully awaited; loaderPromise, created once outside the loop and re-awaited each iteration despite being already settled).
  • Added a comment recording why re-typing cannot work, so the retry is not reintroduced.
  • Added test.slow() to the failing test, matching its sibling that uses the same helper.

Net −12 lines; the helper now matches the shape of searchUserByEmail in the same file.

The removed poll was there to absorb ES index lag, but since its retry was inert the helper has in practice always been a single search — and it passes nearly always, which is itself the evidence that index lag is not a real factor on this path. If a user genuinely is not indexed, expect(userRow).toBeVisible() now fails with a clear message pointing at a real bug rather than a loop hiding it.

Type of change:

  • Bug fix

High-level design:

N/A — small change.

Tests:

Use cases covered

  • Admin navigates to Settings → Members → Users, searches for a user, and opens that user's profile page (visitUserProfilePage, used by UserDetails.spec.ts and other specs).

Unit tests

Not applicable — this PR only changes Playwright test code.

Backend integration tests

  • Not applicable (no backend API changes).

Ingestion integration tests

  • Not applicable (no ingestion changes).

Playwright (UI) tests

This PR is a Playwright fix; no product code changed, so no new test is added.

  • Files updated:
    • openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts
    • openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts

Manual testing performed

Static verification only — I do not have a live stack available:

  1. npx tsc --noEmit -p playwright/tsconfig.json — no new errors. The two remaining TS6133 'displayName' reports are pre-existing, in softDeleteUserProfilePage / hardDeleteUserProfilePage, untouched by this PR.
  2. npx eslint playwright/utils/user.ts playwright/e2e/Pages/UserDetails.spec.ts — 0 errors. The 2 warnings on the spec are pre-existing browser.newPage() notices that the rule itself documents as expected for multi-user tests.
  3. npx prettier --check — clean.
  4. Root cause confirmed by reading the product code rather than by reproduction: the debounce interval, handleSearch, and the fetch effect's dependency array are cited with line numbers above.

Reviewer note: the flake is a race, so please run the spec against a live stack before merging to confirm — yarn playwright:run playwright/e2e/Pages/UserDetails.spec.ts --repeat-each=5.

UI screen recording / screenshots:

Not applicable — no product code changed, test-only.

Checklist:

  • I have read the CONTRIBUTING document.
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: not applicable.
  • For UI changes: not applicable, test-only change.
  • The change is itself a test fix; the repaired visitUserProfilePage covers the exact scenario that was hanging.

🤖 Generated with Claude Code

Greptile Summary

The PR simplifies visitUserProfilePage to issue one deterministic user search and increases the timeout for the previously flaky scenario.

  • Removes the ineffective polling and repeated response waits from the shared Playwright helper.
  • Waits for the user-list loader around the search request before opening the matching row.
  • Marks the inherited-domain user-profile test as slow.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts Replaces the deadlocking retry loop with a single synchronized search, loader wait, visibility assertion, and row click.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts Marks the affected end-to-end scenario as slow to provide an appropriate execution budget.

Reviews (3): Last reviewed commit: "address gitar" | Re-trigger Greptile

… search

visitUserProfilePage retried its user-list search by clearing the search box
and re-typing the same username inside an expect.poll. That retry can never
work: SearchBar debounces at 1s, so fill('') + fill(userName) collapse into a
single handleSearch(userName), and UserListPageV1 refetches only when its
searchValue changes. Re-entering the term already in the box leaves searchValue
untouched, so no request is dispatched and the poll's unbounded
waitForResponse waits forever.

The poll budget also equalled the 60s test timeout, so the test died mid-poll
and reported the raw 49s wait instead of the poll's own message.

Replace the loop with a single search: the box is empty on arrival, so one
fill is a real value change and always dispatches. Scope the response predicate
to the username so a stray query from another component cannot satisfy it, and
drop two dead waits that were created once and never meaningfully awaited.

Add test.slow() to the team-domain-inheritance test, which does two profile
visits plus five API round trips; its sibling using the same helper already
has it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@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.

@harsh-vador harsh-vador added safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch labels Aug 17, 2026
@github-actions github-actions Bot added the UI UI specific issues label Aug 17, 2026
@harsh-vador harsh-vador added skip-pr-checks Bypass PR metadata validation check and removed UI UI specific issues labels Aug 17, 2026
Comment thread openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts
@github-actions github-actions Bot added the UI UI specific issues label Aug 17, 2026
@harsh-vador
harsh-vador enabled auto-merge August 17, 2026 18:49
@harsh-vador

Copy link
Copy Markdown
Contributor Author

Good catch — accepted and fixed in 2617a66. The concern is real, and the divergence is actually wider than encoder mismatch: the frontend never percent-encodes q at all.

rawSearchQuery interpolates the term straight into the URL string rather than passing it through axios params:

// src/rest/searchAPI.ts:187
const apiUrl = `/search/query?q=${apiQuery}${filters ?? ''}`;

So there are two independent ways the predicate could miss:

Source Frontend emits encodeURIComponent produces
searchAPI.ts:187 — raw interpolation, no encoding literal @ : , $ %40 %3A %2C %24
StringUtils.ts:90getQueryWithSlash backslash-escapes quotes \" %22, no backslash

Either one means response.url().includes(...) never matches and the wait hangs to the default timeout — exactly the failure class this PR removes, so it would have been a self-inflicted regression.

Fixed as suggested, matching the URL shape rather than the term:

const searchResponse = page.waitForResponse(
  '/api/v1/search/query?q=*&index=user&from=0&size=*'
);

Kept index=user rather than searchUserByEmail's index=* so the wait stays pinned to the user-list search instead of matching any search on the page — costs nothing and is encoding-independent. Correctness of which user came back is still covered by the expect(userRow).toBeVisible() assertion right after, per your suggestion.

from=0 is safe to pin: handleSearch resets paging to INITIAL_PAGING_VALUE before the fetch effect runs.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit 2617a66ab017d991b6c27b66410fde9196a400bd in Playwright run 32058122036, attempt 2.

✅ 559 passed · ❌ 0 failed · 🟡 1 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 1h 9m 33s

⏱️ Max setup 2m 57s · max shard execution 20m 55s · max shard-job elapsed before upload 24m 19s · reporting 5s

🌐 213.02 requests/attempt · 2.81 app boots/UI scenario · 11.31% common-shard skew

Optimization targets still in progress:

  • Browser traffic was 213.02 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.81 per UI scenario (1637 boots / 582 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 148 0 0 0 0 0
🟡 Shard chromium-02 133 0 1 0 0 0
✅ Shard chromium-03 126 0 0 0 0 0
✅ Shard data-asset-rules-01 61 0 0 0 0 0
✅ Shard domain-isolation-01 14 0 0 0 0 0
✅ Shard global-state-01 34 0 0 0 0 0
✅ Shard ingestion-01 1 0 0 0 0 0
✅ Shard reindex-01 2 0 0 0 0 0
✅ Shard search-01 11 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0
🟡 1 flaky test(s) (passed on retry)
  • Pages/Entity.spec.tsTag Add, Update and Remove for child entities (shard chromium-02, 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

@harsh-vador
harsh-vador added this pull request to the merge queue Aug 18, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-18T19:30:33Z)

Blocked the queue: playwright-summary

  • Postgresql PR Playwright E2E Tests — playwright-summary, playwright / playwright-ci (chromium-19), playwright / playwright-ci (chromium-07), playwright / playwright-ci (chromium-01)

@harsh-vador
harsh-vador added this pull request to the merge queue Aug 19, 2026
Merged via the queue into main with commit 7b4160c Aug 19, 2026
97 of 99 checks passed
@harsh-vador
harsh-vador deleted the fix/flaky-visit-user-profile-page branch August 19, 2026 13:37
@github-actions

Copy link
Copy Markdown
Contributor

Changes have been cherry-picked to the 1.13 branch.

github-actions Bot pushed a commit that referenced this pull request Aug 19, 2026
…an undispatched search (#31647)

* test(playwright): fix visitUserProfilePage hanging on an undispatched search

visitUserProfilePage retried its user-list search by clearing the search box
and re-typing the same username inside an expect.poll. That retry can never
work: SearchBar debounces at 1s, so fill('') + fill(userName) collapse into a
single handleSearch(userName), and UserListPageV1 refetches only when its
searchValue changes. Re-entering the term already in the box leaves searchValue
untouched, so no request is dispatched and the poll's unbounded
waitForResponse waits forever.

The poll budget also equalled the 60s test timeout, so the test died mid-poll
and reported the raw 49s wait instead of the poll's own message.

Replace the loop with a single search: the box is empty on arrival, so one
fill is a real value change and always dispatches. Scope the response predicate
to the username so a stray query from another component cannot satisfy it, and
drop two dead waits that were created once and never meaningfully awaited.

Add test.slow() to the team-domain-inheritance test, which does two profile
visits plus five API round trips; its sibling using the same helper already
has it.

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

* address gitar

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7b4160c)
@github-actions

Copy link
Copy Markdown
Contributor

Changes have been cherry-picked to the 2.0 branch.

github-actions Bot pushed a commit that referenced this pull request Aug 19, 2026
…an undispatched search (#31647)

* test(playwright): fix visitUserProfilePage hanging on an undispatched search

visitUserProfilePage retried its user-list search by clearing the search box
and re-typing the same username inside an expect.poll. That retry can never
work: SearchBar debounces at 1s, so fill('') + fill(userName) collapse into a
single handleSearch(userName), and UserListPageV1 refetches only when its
searchValue changes. Re-entering the term already in the box leaves searchValue
untouched, so no request is dispatched and the poll's unbounded
waitForResponse waits forever.

The poll budget also equalled the 60s test timeout, so the test died mid-poll
and reported the raw 49s wait instead of the poll's own message.

Replace the loop with a single search: the box is empty on arrival, so one
fill is a real value change and always dispatches. Scope the response predicate
to the username so a stray query from another component cannot satisfy it, and
drop two dead waits that were created once and never meaningfully awaited.

Add test.slow() to the team-domain-inheritance test, which does two profile
visits plus five API round trips; its sibling using the same helper already
has it.

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

* address gitar

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 7b4160c)
@gitar-bot

gitar-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 resolved / 1 findings

Replaces the deadlocking search retry loop in visitUserProfilePage with a deterministic single-username search and extends the team-domain test timeout, addressing the waitForResponse predicate matching issue.

✅ 1 resolved
Edge Case: waitForResponse predicate may not match encoded usernames

📄 openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts:131-137
The response predicate matches on response.url().includes(encodeURIComponent(userName)). encodeURIComponent and the frontend's actual query-string encoder can diverge for non-alphanumeric characters (e.g. spaces become %20 vs +, @ handling, etc.). If a username contains such characters the predicate never matches and await searchResponse hangs until the default timeout — reintroducing the exact hang class this PR fixes. The sibling helper searchUserByEmail (user.ts:55-57) avoids this by matching only the stable URL shape (/api/v1/search/query?q=*&index=*&from=0&size=*). Consider dropping the username portion of the predicate (relying on the fill + toBeVisible check to validate the correct row appears) unless usernames are guaranteed alphanumeric.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

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 skip-pr-checks Bypass PR metadata validation check To release Will cherry-pick this PR into the release branch UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants