Skip to content

test(ui): make glossary listing tests resilient to active-glossary drift#28988

Open
siddhant1 wants to merge 4 commits into
mainfrom
pw-fix-glossary-active-glossary-drift
Open

test(ui): make glossary listing tests resilient to active-glossary drift#28988
siddhant1 wants to merge 4 commits into
mainfrom
pw-fix-glossary-active-glossary-drift

Conversation

@siddhant1

@siddhant1 siddhant1 commented Jun 12, 2026

Copy link
Copy Markdown
Member

Problem

Several Glossary.spec.ts listing-page tests (e.g. "Approve and reject glossary term from Glossary Listing") are flaky. The glossary listing page intentionally falls back to the first glossary (glossaries[0]) when the URL's glossary isn't present in the freshly loaded sidebar page (GlossaryPage.component.tsx: setActiveGlossary(find(fqn) || glossaries[0])).

Under parallel test load the sidebar glossary list churns (other workers create/delete glossaries; infinite-scroll pagination re-sets the list), so the in-memory active glossary drifts to another worker's glossary even though the browser URL stays correct. The terms table then renders the wrong glossary's children, and the just-created term's row ([data-row-key="<fqn>"]) never appears — the assertion times out.

Trace evidence from the failing run: after the 2nd term POST (201) into glossary Zany, the table refetched directChildrenOf="…Quiet…" (a different glossary); the URL never left Zany. Pure in-memory state drift.

Note: the find(fqn) || glossaries[0] fallback is intended product behavior, so the fix is test-side.

Change (test-only)

A shared assertWithReloadRecovery helper that, on an assertion miss, reloads (which re-derives the active glossary from the URL FQN) and retries under toPass. Applied at the two drift-exposed points:

  • selectActiveGlossary — the universal re-pin chokepoint. The sidebar highlight is URL-driven, so a re-click is a no-op when drifted; it now verifies the header shows the requested glossary and reloads to re-pin if it drifted.
  • validateGlossaryTerm — the actual failure point. The term-creation loop drifts between terms without re-selecting, so the term-row check now self-heals via reload.

No app/product code touched.

Testing

  • yarn playwright:run --grep "Approve and reject glossary term from Glossary Listing"
  • yarn lint (playwright) — only pre-existing warnings
  • no new tsc errors in the changed file

Candidate for cherry-pick to 1.12.11 (same code path / same flake reported there).

🤖 Generated with Claude Code

Greptile Summary

This PR adds a assertWithReloadRecovery test utility to glossary.ts that handles in-memory active-glossary drift caused by parallel test workers mutating the sidebar glossary list, without touching any product code.

  • assertWithReloadRecovery wraps a Playwright assertion in a try/catch; on failure it enters a toPass loop that reloads the page (re-deriving the active glossary from the URL) and retries, with intervals of 2 s / 5 s / 10 s and a 30 s total budget.
  • Applied at two drift-exposed sites: selectActiveGlossary (verifies the entity header reflects the requested glossary after sidebar click) and validateGlossaryTerm (guards the term-row toBeVisible check that was the observed failure point).

Confidence Score: 4/5

Test-only change with no product code touched; the reload recovery logic is sound but has two minor gaps worth addressing before relying on it for long-term flakiness suppression.

The new helper correctly exploits the fact that the URL stays accurate during drift and a reload re-derives active glossary state from it. The two concerns — unprotected assertions that follow the guarded toBeVisible check in validateGlossaryTerm, and a toPass budget that may only allow one reload on slower CI agents — are non-blocking but could mean the fix is only partially effective under adverse conditions.

openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts — specifically the assertions following assertWithReloadRecovery in validateGlossaryTerm (lines 748–758) and the toPass timeout/interval configuration in the helper itself.

Important Files Changed

Filename Overview
openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts Adds assertWithReloadRecovery helper and applies it to selectActiveGlossary and validateGlossaryTerm to handle in-memory active-glossary drift under parallel test load; the approach is sound but assertions after the reload-guarded check in validateGlossaryTerm remain unprotected and the toPass retry budget may allow fewer reloads than intended on slow CI

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["assertWithReloadRecovery(page, verify)"] --> B["try: await verify()"]
    B -->|passes| C["Done"]
    B -->|throws| D["enter toPass loop\ntimeout: 30s\nintervals: 2s/5s/10s"]
    D --> E["page.reload()"]
    E --> F["waitForAllLoadersToDisappear(page)"]
    F --> G["await verify() with timeout: 10s"]
    G -->|passes| H["toPass succeeds"]
    G -->|throws| I{Wall clock < 30s?}
    I -->|yes, wait interval| E
    I -->|no| J["toPass throws last error"]

    subgraph selectActiveGlossary
        SA1["click sidebar menu item"] --> SA2["waitForAllLoadersToDisappear"]
        SA2 --> SA3["assertWithReloadRecovery:\nentity-header-display-name contains label"]
    end

    subgraph validateGlossaryTerm
        VT1["wait for table to load"] --> VT2["assertWithReloadRecovery:\ndata-row-key=fqn is visible"]
        VT2 --> VT3["toContainText(term.name) - not reload-protected"]
        VT3 --> VT4["statusSelector checks - not reload-protected"]
    end
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"}}}%%
flowchart TD
    A["assertWithReloadRecovery(page, verify)"] --> B["try: await verify()"]
    B -->|passes| C["Done"]
    B -->|throws| D["enter toPass loop\ntimeout: 30s\nintervals: 2s/5s/10s"]
    D --> E["page.reload()"]
    E --> F["waitForAllLoadersToDisappear(page)"]
    F --> G["await verify() with timeout: 10s"]
    G -->|passes| H["toPass succeeds"]
    G -->|throws| I{Wall clock < 30s?}
    I -->|yes, wait interval| E
    I -->|no| J["toPass throws last error"]

    subgraph selectActiveGlossary
        SA1["click sidebar menu item"] --> SA2["waitForAllLoadersToDisappear"]
        SA2 --> SA3["assertWithReloadRecovery:\nentity-header-display-name contains label"]
    end

    subgraph validateGlossaryTerm
        VT1["wait for table to load"] --> VT2["assertWithReloadRecovery:\ndata-row-key=fqn is visible"]
        VT2 --> VT3["toContainText(term.name) - not reload-protected"]
        VT3 --> VT4["statusSelector checks - not reload-protected"]
    end
Loading

Comments Outside Diff (1)

  1. openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts, line 745-758 (link)

    P2 Unprotected assertions after reload recovery

    assertWithReloadRecovery guards the toBeVisible check and reloads when drifted, but the four subsequent assertions (toContainText, statusSelector visible, status text) run outside the helper against the post-reload page. If the component re-renders mid-assertion (e.g., an in-flight terms API response resolves after the reload and repopulates the wrong glossary's children again), those checks can still fail. The drift window is small but not zero — a second parallel-worker mutation arriving just after the reload could trigger it again before those assertions complete.

    Consider folding all five assertions into a single assertWithReloadRecovery call, or re-selecting the active glossary before proceeding past the reload.

Reviews (1): Last reviewed commit: "Merge branch 'main' into pw-fix-glossary..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

The glossary listing page falls back to the first glossary (glossaries[0])
when the URL's glossary isn't present in the freshly loaded sidebar page.
Under parallel test load the sidebar list churns, so the in-memory active
glossary can drift to another worker's glossary even though the URL stays
correct (the sidebar highlight is URL-driven, so a re-click is a no-op),
leaving the terms table showing the wrong glossary. This flaked tests such
as "Approve and reject glossary term from Glossary Listing" at the term-row
visibility assertion.

Recover by reloading -- which re-derives the active glossary from the URL
FQN -- in selectActiveGlossary and validateGlossaryTerm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@siddhant1
siddhant1 requested a review from a team as a code owner June 12, 2026 06:16
Copilot AI review requested due to automatic review settings June 12, 2026 06:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Jun 12, 2026

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

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@siddhant1 siddhant1 added UI UI specific issues 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 Jun 12, 2026
Comment thread openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 62%
62.36% (66719/106990) 44.03% (37208/84494) 45.51% (11306/24839)

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

🔴 Playwright Results — 1 failure(s), 16 flaky

✅ 4283 passed · ❌ 1 failed · 🟡 16 flaky · ⏭️ 88 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 1 299 0 3 4
🟡 Shard 2 808 0 1 9
🟡 Shard 3 810 0 3 8
🟡 Shard 4 846 0 3 12
🟡 Shard 5 730 0 3 47
🔴 Shard 6 790 1 3 8

Genuine Failures (failed on all attempts)

Pages/Glossary.spec.ts › Verify Glossary Deny Permission (shard 6)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoContainText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator: getByTestId('entity-header-display-name')
Expected substring: �[32m"PW % 5eb356a9 Bolda3ab0566"�[39m
Timeout: 10000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toContainText" with timeout 10000ms�[22m
�[2m  - waiting for getByTestId('entity-header-display-name')�[22m


Call Log:
- Test timeout of 60000ms exceeded
🟡 16 flaky test(s) (passed on retry)
  • Features/CustomizeDetailPage.spec.ts › Domain - customization should work (shard 1, 1 retry)
  • Features/MetricCustomUnitFlow.spec.ts › Should create metric and test unit of measurement updates (shard 1, 1 retry)
  • Pages/Roles.spec.ts › Roles page should work properly (shard 1, 1 retry)
  • Features/Glossary/GlossaryHierarchy.spec.ts › should cancel move operation (shard 2, 1 retry)
  • Features/KnowledgeCenterList.spec.ts › Knowledge Center List - Verify Recently Viewed widget (shard 3, 1 retry)
  • Features/Tasks/TaskNavigation.spec.ts › navigating to /table/TASK-XXXXX should show 404 (invalid URL pattern) (shard 3, 1 retry)
  • Features/Workflows/NoOpWorkflowNodeConfig.spec.ts › schema fields for runAppTask node render with correct labels and values (shard 3, 1 retry)
  • Pages/CustomProperties.spec.ts › Hyperlink (shard 4, 1 retry)
  • Pages/DomainUIInteractions.spec.ts › Add owner to domain via UI (shard 4, 1 retry)
  • Pages/Entity.spec.ts › Delete Table (shard 4, 1 retry)
  • Pages/Entity.spec.ts › User as Owner with unsorted list (shard 5, 1 retry)
  • Pages/Entity.spec.ts › Inactive Announcement create & delete (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 5, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for mlModel -> table (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/UserDetails.spec.ts › Create team with domain and verify visibility of inherited domain in user profile after team removal (shard 6, 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

Copilot AI review requested due to automatic review settings June 12, 2026 09:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gitar-bot

gitar-bot Bot commented Jun 17, 2026

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

Adds a assertWithReloadRecovery helper to resolve flaky glossary tests caused by in-memory active-glossary drift. Note that the recovery path can extend total assertion runtime up to 40 seconds during a reload.

✅ 1 resolved
Quality: Recovery path can extend assertion runtime up to ~40s

📄 openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts:66-79 📄 openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts:110-115 📄 openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts:745-747
assertWithReloadRecovery first runs verify() (which itself carries a 10s expect timeout), and only on failure enters the toPass retry with a 30s timeout. In the worst case a single drifted assertion now costs ~10s + ~30s ≈ 40s before failing. Across the term-creation loop in validateGlossaryTerm and every selectActiveGlossary re-pin, this can noticeably inflate total spec runtime when drift is frequent, and could push individual steps toward the global test timeout.

This is acceptable for the resilience goal, but consider trimming the inner verify() timeout (e.g. to 3-5s) so the fast path fails quickly and hands off to the reload-based recovery sooner, keeping the worst-case bounded. Not blocking — the helper is otherwise correct and genuine failures still surface after the budget expires.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Comment on lines +66 to +79
const assertWithReloadRecovery = async (
page: Page,
verify: () => Promise<void>
) => {
try {
await verify();
} catch {
await expect(async () => {
await page.reload();
await waitForAllLoadersToDisappear(page);
await verify();
}).toPass({ intervals: [2_000, 5_000, 10_000], timeout: 30_000 });
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Retry budget may not allow more than one reload in practice

toPass is called with { intervals: [2_000, 5_000, 10_000], timeout: 30_000 }. Each iteration runs page.reload() + waitForAllLoadersToDisappear + verify() (which itself has a 10_000 ms internal timeout on failure). In a slow CI environment where reload + loader settling takes ~8–12 s, the first retry attempt alone consumes ~20–22 s, leaving only ~8–10 s for a second attempt before the 30 s wall hits. The intervals effectively become dead time that cannot be recovered, so the retry count is far lower than the three-slot array implies.

Raising timeout to 60_000 or reducing verify's internal timeout to e.g. 5_000 would make the budget more reliable without changing the intent.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

This PR has had no activity for 30 days and will be closed in 7 days if no further activity occurs.
Feel free to reopen it if you'd like to continue working on it.

@github-actions github-actions Bot added the Stale label Jul 18, 2026
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 Stale 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.

3 participants