Skip to content

Stabilize flaky Playwright profile tests#30265

Open
harsh-vador wants to merge 21 commits into
mainfrom
fix-playwright-flakiness
Open

Stabilize flaky Playwright profile tests#30265
harsh-vador wants to merge 21 commits into
mainfrom
fix-playwright-flakiness

Conversation

@harsh-vador

@harsh-vador harsh-vador commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make user persona profile saves wait on PATCH responses before continuing
  • add loader waits and response validation after persona saves
  • harden Explore right-panel overview actions against blocking alert overlays and slow PATCH responses

Test Plan

  • git diff --check
  • git -C OpenMetadata diff --check
  • scanned added Playwright lines for waitForTimeout, networkidle, force: true, and new positional selectors

Note: yarn lint:playwright and yarn tsc:playwright could not run in this workspace because Yarn fails before invoking the scripts with: open-metadata@workspace:. is missing from the lockfile.


Summary by Gitar

  • Governance workflows:
    • Bound concurrent task resolutions to 8 threads using a Semaphore in WorkflowHandler to prevent connection pool exhaustion.
    • Added a signalPolicyAgentResult method to allow manual triggering of parked DAR workflow nodes.
    • Enabled manualGrantReason passthrough for task payloads in CreateTask.
  • Task assignment logic:
    • Updated TaskRepository to expand team owners into individual member users when defaulting assignees for new tasks.

This will update automatically on new commits.

Greptile Summary

This PR expands Policy Agent workflow support and strengthens Playwright save synchronization. The main changes are:

  • Adds complete failure reporting for opted-in ingestion steps.
  • Adds Policy Agent settings, result signaling, and manual-grant reasons.
  • Expands team owners into task assignees and limits concurrent task resolutions.
  • Waits for user and Explore PATCH requests, loaders, and alert overlays.

Confidence Score: 4/5

Task assignment and Policy Agent signaling can silently take the wrong fallback path. These paths need fixes before merging.

  • A failed team lookup can remove the configured owner and route approval to administrators.
  • Unexpected Flowable delivery failures are hidden, delaying requests until the safety timer.
  • Broad alert waits can introduce deterministic Playwright timeouts.
  • The schema, generated types, and normal save-response synchronization are aligned.

TaskRepository.java, WorkflowHandler.java, and OverviewPageObject.ts.

Security Review

A failed team-owner expansion can remove the configured approval audience and invoke administrator fallback. This can route an approval to principals who were not listed as the entity owner.

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TaskRepository.java Expands owning teams into users, but a failed lookup can silently remove the configured assignee.
openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/WorkflowHandler.java Adds task-resolution admission control and Policy Agent signaling, but suppresses unexpected message-delivery failures.
openmetadata-service/src/main/java/org/openmetadata/service/governance/workflows/elements/nodes/userTask/CreateTask.java Adds optional manual-grant reasons to task payloads on creation and update.
ingestion/src/metadata/ingestion/api/step.py Allows selected steps to return their complete failure list while retaining the default cap.
openmetadata-ui/src/main/resources/ui/playwright/e2e/PageObject/Explore/OverviewPageObject.ts Adds response, loader, and alert waits, but the alert wait also blocks on non-overlay alerts.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts Registers user PATCH waits before clicks and validates successful responses before continuing.
openmetadata-spec/src/main/resources/json/schema/configuration/workflowSettings.json Adds optional Policy Agent coordinator settings with positive integer constraints.
openmetadata-spec/src/main/resources/json/schema/type/dataAccessRequestPayload.json Adds the optional manualGrantReason payload field.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant DAR as Data Access Request
    participant PA as Policy Agent Coordinator
    participant DB as Outcome Store
    participant WF as Workflow Engine
    PA->>DB: Write per-policy outcomes
    PA->>WF: Send policyAgentResult message
    WF->>DB: Read outcomes
    WF-->>DAR: Continue grant or manual approval
    Note over WF,DAR: Safety timer resumes requests when a signal is missed
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 DAR as Data Access Request
    participant PA as Policy Agent Coordinator
    participant DB as Outcome Store
    participant WF as Workflow Engine
    PA->>DB: Write per-policy outcomes
    PA->>WF: Send policyAgentResult message
    WF->>DB: Read outcomes
    WF-->>DAR: Continue grant or manual approval
    Note over WF,DAR: Safety timer resumes requests when a signal is missed
Loading

Reviews (1): Last reviewed commit: "test: stabilize flaky Playwright profile..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Context used (3)

  • Context used - CLAUDE.md (source)
  • Context used - openmetadata-ui-core-components/CLAUDE.md (source)
  • Context used - AGENTS.md (source)

yan-3005 and others added 21 commits June 5, 2026 14:48
… reason

Supporting OpenMetadata changes for the Collate Data Access Request (DAR)
concurrency work — clubbing concurrent policy-agent grants into a single
ingestion run.

- workflowSettings: add policyAgentConfiguration (pollingIntervalSeconds,
  batchWindowSeconds, batchMaxSize), read by the Collate batch coordinator.
- policyAgentTaskDefinition: add pollingIntervalSeconds node config.
- CreateTask: optionally carry a workflow-supplied reason into the created
  task's payload as `manualGrantReason` (its own structured field). Strict
  no-op (try/catch + instanceof) for workflows that do not set it, so generic
  approval workflows (Glossary, etc.) are byte-for-byte unaffected.
- Tests: CreateTaskManualGrantReasonTest; the 44 generic CreateTaskTest pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- workflowSettings.policyAgentConfiguration: add `minimum: 1` to
  pollingIntervalSeconds / batchWindowSeconds / batchMaxSize; add
  `batchWorkerThreads` (default 4) so the coordinator's worker-pool size
  is configurable instead of a hard-coded cap.
- policyAgentTaskDefinition: add `minimum: 1` to pollingIntervalSeconds and
  correct the fallback doc path to
  workflowSettings.policyAgentConfiguration.pollingIntervalSeconds.
- Regenerate UI TypeScript for WorkflowSettings and PolicyAgentTaskDefinition
  so the generated types stay in sync with the schema.
- Align the new test's license-header year (2026) with neighbouring tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er-wide default

Removing the per-node default (30) makes the generated field nullable, so an
unset node value is now distinguishable from a configured one. The Collate
PolicyAgentTask resolves it to the node override → server-wide
policyAgentConfiguration.pollingIntervalSeconds → built-in default, which is
what the field description already promised. (minimum:1 stays.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mergeManualGrantReason called JsonUtils.getMap (a Jackson convertValue) even
when the payload was already a Map, which can re-type nested values and can
throw. Copy the existing map entries directly (mirroring withGrantExpirationDate)
and only fall back to a guarded conversion for the unexpected non-Map case, so
task creation is never broken by a conversion issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary.from_step caps the reported failure details to the first 10 (status
payload size). That's fine for diagnostics but unsafe when downstream logic needs
the COMPLETE per-item failure list — e.g. the Collate Policy Agent batch
coordinator, which decides each clubbed request's outcome (grant vs manual) from
exactly which policy ids failed; a truncated list silently grants the un-reported
failures. Add an opt-in `report_all_failures` flag (default False → every
existing connector keeps the 10-cap). The error COUNT was already accurate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace per-DAR timer polling with an event-based gateway that races the
coordinator's result signal against a safety timer. WorkflowHandler delivers
POLICY_AGENT_RESULT_MESSAGE to wake a parked DAR the moment its clubbed batch
resolves; the timer is the fallback if the signal is ever missed, and the node
re-reads the authoritative DB rows on wake so a missed signal self-corrects.

- WorkflowHandler.signalPolicyAgentResult: wake parked DAR(s) by process
  instance; list() + per-execution delivery so a shared message name with
  multiple parked nodes or a raced subscription never strands a request.
- policyAgentTaskDefinition: drop dead node-level pollingIntervalSeconds (the
  push design no longer polls); add minimum 1 to timeoutSeconds so a 0 value
  cannot make the safety timer fire instantly and beat the signal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e's connection pool

A burst of concurrent task approvals (e.g. many Data Access Requests approved at
once) had each resolution call taskService.complete() synchronously on the
request thread, all contending for Flowable's own MyBatis connection pool
(default 10 active). Excess threads blocked in PooledDataSource.popConnection
past the client timeout and left tasks stranded in review.

Add a fair admission semaphore (MAX_CONCURRENT_TASK_RESOLUTIONS) around
resolveTaskInternal: only a bounded number of resolutions touch Flowable at once;
the rest queue efficiently in-JVM (FIFO) and drain in order. The pool is never
driven to exhaustion, so other governance workflows keep their connections. This
bounds the failure mode from catastrophic pool-stampede to graceful queueing — it
does not add capacity (a finite pool always has a ceiling), it makes the existing
capacity usable without the popConnection pathology.

Verified with a 100-wide unpaced approval burst: 100/100 resolved, 0 timeouts,
0 dead-letters, DB load flat vs idle.

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

When a task defaults its assignees to the target entity's owners, a Team owner
was assigned as-is. Expand Team references to their member users so the task is
assigned to actual people rather than a team placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve policyAgentTaskDefinition.json: keep both the timeoutSeconds minimum:1
(this branch) and the accessType override field (revoke work from main).
…e report_all_failures, keep admin fallback

- CreateTask: include the caught exception in the manualGrantReason merge warn log,
  and debug-log (not silently swallow) a real lookup error in resolveManualGrantReason.
- step.py: declare `report_all_failures = False` on the base `Step` class (so the
  opt-out is discoverable, not just a getattr string) and reformat the `failures=`
  conditional to stay under the Ruff line-length limit.
- TaskRepository.expandTeamsToUsers: a team with no members intentionally contributes
  no assignees so the empty list triggers the node's assignAdmins fallback in
  SetApprovalAssigneesImpl (workflow-managed DARs route to platform admins) rather
  than pinning the task to a member-less team. Documented to avoid a future "regression".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Optional, system-set field carrying why a data access request was routed to
manual approval (partial auto-grant, enforcement failure, or timeout). Absent
on the automatic path. Regenerated the TypeScript type.

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

ruff format the report_all_failures conditional (fixes py-checkstyle) and add an
inline pyright ignore for the pre-existing TruncatedStackTraceError vs
StackTraceError invariance that the reformat un-baselined (fixes static-checks).
Add a unit test for the 10-failure display cap and the report_all_failures opt-out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fy polling scope

- Extract the report_all_failures display-cap logic into a readable local instead of
  a nested ternary. No behavior change.
- Clarify workflowSettings.pollingIntervalSeconds: it is the batch coordinator's poll
  of the ingestion run; the Data Access Request workflow node is signalled (pushed),
  not polled. Drops the stale per-node-override claim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@harsh-vador
harsh-vador requested review from a team as code owners July 21, 2026 05:27
@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

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!

@github-actions

Copy link
Copy Markdown
Contributor

🔴 Playwright Results — 16 pipeline/setup failure(s)

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

Pipeline and setup failures

  • The build job finished with status failure.
  • The Playwright shard matrix was unexpectedly skipped.
  • Shard 1 did not upload a usable Playwright results artifact.
  • Shard 2 did not upload a usable Playwright results artifact.
  • Shard 3 did not upload a usable Playwright results artifact.
  • Shard 4 did not upload a usable Playwright results artifact.
  • Shard 5 did not upload a usable Playwright results artifact.
  • Shard 6 did not upload a usable Playwright results artifact.
  • Shard 7 did not upload a usable Playwright results artifact.
  • Shard 1 did not upload its execution status.
  • Shard 2 did not upload its execution status.
  • Shard 3 did not upload its execution status.
  • Shard 4 did not upload its execution status.
  • Shard 5 did not upload its execution status.
  • Shard 6 did not upload its execution status.
  • Shard 7 did not upload its execution status.
Shard Passed Failed Flaky Skipped
⛔ Shard 1
⛔ Shard 2
⛔ Shard 3
⛔ Shard 4
⛔ Shard 5
⛔ Shard 6
⛔ Shard 7

📦 Download artifacts

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

Comment on lines +476 to +479
} catch (Exception e) {
LOG.debug(
"Failed to expand team {} to users: {}", ref.getFullyQualifiedName(), e.getMessage());
}

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.

P1 security Failed Expansion Changes Assignees

When an owning team cannot be loaded because of a stale reference or transient repository failure, this branch drops that owner and still installs the partial expanded list. The old path retained the team assignee; the new path can create an unassigned task or trigger the workflow's administrator fallback, routing approval away from the configured owner.

Context Used: CLAUDE.md (source)

Comment on lines +1423 to +1429
} catch (FlowableException ex) {
// Subscription consumed/removed between query and delivery (timer won the race, or a
// concurrent signal): safe to ignore — the node resolves from the DB rows regardless.
LOG.debug(
"PolicyAgent result signal no-op for execution {}: {}",
execution.getId(),
ex.getMessage());

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.

P1 Engine Failures Become Lost Signals

Only a missing or concurrently consumed subscription is a safe no-op, but this catches every FlowableException. A database or optimistic-lock failure is therefore hidden at DEBUG level, so the coordinator returns as if delivery succeeded while the request remains parked until its safety timer.

Context Used: CLAUDE.md (source)

Comment on lines +45 to +49
protected async waitForBlockingAlertsToClear(): Promise<void> {
await expect(this.page.getByTestId('alert-bar')).toHaveCount(0, {
timeout: 15_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 Persistent Alerts Block Every Edit

This treats every alert-bar as a blocking overlay and waits for the element to be removed. A persistent or manually dismissible alert can leave the target control usable but make each description, tag, or glossary action time out after 15 seconds; the wait should target the overlay that actually intercepts these controls.

Context Used: CLAUDE.md (source)

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!

Comment on lines +459 to +473
private List<EntityReference> expandTeamsToUsers(List<EntityReference> refs) {
List<EntityReference> result = new ArrayList<>();
for (EntityReference ref : refs) {
if (!Entity.TEAM.equals(ref.getType())) {
result.add(ref);
continue;
}
try {
Team team = Entity.getEntity(Entity.TEAM, ref.getId(), "users", Include.NON_DELETED);
if (!nullOrEmpty(team.getUsers())) {
result.addAll(team.getUsers());
}
// A team with no members intentionally contributes no assignees: for workflow-managed tasks
// (e.g. Data Access Requests) an empty assignee list triggers the node's
// emptyAssigneeStrategy

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Bug: expandTeamsToUsers can produce duplicate assignees

expandTeamsToUsers concatenates direct user owners and each team's members without de-duplication, so a user who is both a direct owner and a member of an owning team (or a member shared by two owning teams) is added to the assignee list more than once. Downstream updateAssignees tolerates duplicates via removeAll-based set math, but the persisted/echoed assignee list can still surface the same user twice (e.g. duplicate display in the UI) and causes redundant relationship writes. De-duplicate by id before returning.

Key by user id to drop duplicate assignees while preserving order.:

private List<EntityReference> expandTeamsToUsers(List<EntityReference> refs) {
  Map<UUID, EntityReference> result = new LinkedHashMap<>();
  for (EntityReference ref : refs) {
    if (!Entity.TEAM.equals(ref.getType())) {
      result.putIfAbsent(ref.getId(), ref);
      continue;
    }
    try {
      Team team = Entity.getEntity(Entity.TEAM, ref.getId(), "users", Include.NON_DELETED);
      if (!nullOrEmpty(team.getUsers())) {
        team.getUsers().forEach(u -> result.putIfAbsent(u.getId(), u));
      }
    } catch (Exception e) {
      LOG.debug(
          "Failed to expand team {} to users: {}", ref.getFullyQualifiedName(), e.getMessage());
    }
  }
  return new ArrayList<>(result.values());
}
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Jul 21, 2026

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

Stabilizes Playwright profile tests by enforcing PATCH response synchronization and adds robust governance workflow controls. The team-to-user expansion logic requires a de-duplication step to avoid assigning duplicate users to tasks.

💡 Bug: expandTeamsToUsers can produce duplicate assignees

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TaskRepository.java:459-473

expandTeamsToUsers concatenates direct user owners and each team's members without de-duplication, so a user who is both a direct owner and a member of an owning team (or a member shared by two owning teams) is added to the assignee list more than once. Downstream updateAssignees tolerates duplicates via removeAll-based set math, but the persisted/echoed assignee list can still surface the same user twice (e.g. duplicate display in the UI) and causes redundant relationship writes. De-duplicate by id before returning.

Key by user id to drop duplicate assignees while preserving order.
private List<EntityReference> expandTeamsToUsers(List<EntityReference> refs) {
  Map<UUID, EntityReference> result = new LinkedHashMap<>();
  for (EntityReference ref : refs) {
    if (!Entity.TEAM.equals(ref.getType())) {
      result.putIfAbsent(ref.getId(), ref);
      continue;
    }
    try {
      Team team = Entity.getEntity(Entity.TEAM, ref.getId(), "users", Include.NON_DELETED);
      if (!nullOrEmpty(team.getUsers())) {
        team.getUsers().forEach(u -> result.putIfAbsent(u.getId(), u));
      }
    } catch (Exception e) {
      LOG.debug(
          "Failed to expand team {} to users: {}", ref.getFullyQualifiedName(), e.getMessage());
    }
  }
  return new ArrayList<>(result.values());
}
🤖 Prompt for agents
Code Review: Stabilizes Playwright profile tests by enforcing PATCH response synchronization and adds robust governance workflow controls. The team-to-user expansion logic requires a de-duplication step to avoid assigning duplicate users to tasks.

1. 💡 Bug: expandTeamsToUsers can produce duplicate assignees
   Files: openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TaskRepository.java:459-473

   expandTeamsToUsers concatenates direct user owners and each team's members without de-duplication, so a user who is both a direct owner and a member of an owning team (or a member shared by two owning teams) is added to the assignee list more than once. Downstream updateAssignees tolerates duplicates via removeAll-based set math, but the persisted/echoed assignee list can still surface the same user twice (e.g. duplicate display in the UI) and causes redundant relationship writes. De-duplicate by id before returning.

   Fix (Key by user id to drop duplicate assignees while preserving order.):
   private List<EntityReference> expandTeamsToUsers(List<EntityReference> refs) {
     Map<UUID, EntityReference> result = new LinkedHashMap<>();
     for (EntityReference ref : refs) {
       if (!Entity.TEAM.equals(ref.getType())) {
         result.putIfAbsent(ref.getId(), ref);
         continue;
       }
       try {
         Team team = Entity.getEntity(Entity.TEAM, ref.getId(), "users", Include.NON_DELETED);
         if (!nullOrEmpty(team.getUsers())) {
           team.getUsers().forEach(u -> result.putIfAbsent(u.getId(), u));
         }
       } catch (Exception e) {
         LOG.debug(
             "Failed to expand team {} to users: {}", ref.getFullyQualifiedName(), e.getMessage());
       }
     }
     return new ArrayList<>(result.values());
   }

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 👍 / 👎 | Gitar

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants