Skip to content

Playwright guardrails (3/N): burn down positional locators in e2e/Pages - #31028

Closed
ShaileshParmar11 wants to merge 23 commits into
mainfrom
claude/playwright-positional-pages
Closed

Playwright guardrails (3/N): burn down positional locators in e2e/Pages#31028
ShaileshParmar11 wants to merge 23 commits into
mainfrom
claude/playwright-positional-pages

Conversation

@ShaileshParmar11

Copy link
Copy Markdown
Contributor

Describe your changes:

No linked issue yet — needs Fixes #<issue-number> before merge.
Depends on #31026 (the guardrail gate). Merge that first.

PR 3 of a series. Burns down om-playwright/no-positional-locator across playwright/e2e/Pages/.

Positional locators (.first(), .last(), .nth()) are called "not recommended" by Playwright's own docs — when the page changes, Playwright may act on an element you did not intend.

312 sites across 48 files. Suppressions 1,446 → 1,146.

Type of change:

  • Improvement

High-level design:

Four conversion patterns, in priority order:

  1. Delete the positional call where the locator was already unique — most were defensive. Where a locator genuinely matches several elements, Playwright strict mode now fails loudly, which is the desired signal rather than a silent wrong-element click.
  2. Scope by a test-owned identifier.filter({ hasText }), getByRole(role, { name }), a specific data-testid, or getRowByName() from scopedLocators.ts.
  3. Convert to a count assertionloc.first().waitFor({ state: 'detached' }) is exactly expect(loc).toHaveCount(0); existence checks became expect(loc).not.toHaveCount(0).
  4. Justified eslint-disable — last resort, only where position genuinely is the semantic. Each carries a -- <why> justification, enforced at error by the justified-rule-disable rule.

The most substantial finding is in the query-builder specs. 54 sites in DataContractsSemanticRules.spec.ts plus a cluster in DataContracts.spec.ts all tracked rows with page-wide .nth(). Reading the @react-awesome-query-builder source showed why: .group matches two elements per instance, because RuleGroup wraps itself in Draggable("group rule_group") — and ExpandableCard never unmounts saved semantics, so the global .group/.rule count kept growing as a test added more. Scoping to .expanded-active-card .rule_group (a class the component already sets on the single card being edited) isolates the active tree and removes the need for positional tracking entirely.

A second recurring fix: for rows with non-deterministic ids, capture the row's real data-testid via .getAttribute() while it is still uniquely resolvable, then address it by identity. data-testid is a reflected HTML attribute; a controlled input's value is a DOM property no selector can see — which is what the original "position is the only stable identifier" justifications missed.

The corpus meta-test's suppression ceiling ratchets from 1,446 to 1,146.

Tests:

Use cases covered

  • Page specs that previously selected rows, cards and grid nodes by position now select by identity, or document why position is correct
  • Existence checks that used .first() are now count assertions, which cannot select a wrong element at all

Unit tests

  • Not applicable — no new logic; yarn test:eslint-rules (7/7) still passes, including the ratcheted ceiling.

Backend integration tests

  • Not applicable.

Ingestion integration tests

  • Not applicable.

Playwright (UI) tests

  • Not applicable as new tests — this modifies existing specs. CI is the verification: a locator that no longer resolves uniquely fails loudly under strict mode rather than passing vacuously.

Manual testing performed

  1. yarn lint:playwright:full → exit 0
  2. yarn test:eslint-rules → 7/7
  3. yarn tsc:playwright → 163 errors, unchanged from the base branch, zero TS2304
  4. --report-unused-disable-directives corpus-wide → zero orphaned directives
  5. Prettier --check on all 48 changed files → clean
  6. Suppression arithmetic verified: the drop equals the sites fixed; no other rule's counts moved

UI screen recording / screenshots:

Not applicable — no application UI changes.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>no issue linked yet
  • My PR is linked to a GitHub issue via Fixes #<issue-number> above — outstanding
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: not applicable.
  • For UI changes: not applicable.
  • Tests listed above.

Merge order

This PR and its siblings all modify eslint-suppressions.json, so they can be reviewed in parallel but must merge sequentially, each rebasing and re-running the full-corpus prune after the previous lands.

🤖 Generated with Claude Code

ShaileshParmar11 and others added 21 commits August 4, 2026 11:59
Adds the CommonJS eslint-rules plugin skeleton (rules: {}) that later
tasks register rules into, wires it into eslint.config.mjs under the
om-playwright namespace, and adds a node --test based unit test target.

Also adds an eslint.config.mjs block scoping the plugin's own .js files
to CommonJS globals/no-require-imports:off, and switches the
test:eslint-rules script to a glob instead of a bare directory path --
both needed because `node --test <dir>` does not recurse into an
explicit directory argument on this repo's pinned Node (22.17.0),
confirmed against the .nvmrc version, not just the locally active one.
…d fix 16 races

Registers a listener promise before the triggering action instead of awaiting
waitForResponse() after it, which races an already-fired response and hangs
until timeout. Zero suppressions — all 16 pre-existing violations fixed by hand.
…n, not as a weaker loader wait

Review found the explore.ts fix from the prior commit substituted a strictly
weaker wait (toHaveCount(0) is a no-op if the loader hasn't mounted yet) with
an inaccurate comment. selectDataAssetFilter has exactly one call site, so
the listener moves to EntitySummaryPanel.spec.ts's single beforeEach, before
the sidebarClick that actually triggers the query.
…test objects

- beforeEach runs on the triggering test's own timeout slot (verified
  against playwright@1.57.0 worker/workerMain.js), so test.slow() there
  has the same blast radius as a describe-scope call; the previous
  heuristic treated it as legal. beforeAll/afterAll/afterEach each get
  their own slot and correctly remain legal.
- Widen detection to aliased test objects (e.g. base.slow() inside
  base.describe(), where base = test.extend(...)) via a dynamic
  test-object-identifier signal, while excluding variables bound
  directly to the describe function itself (const d = test.describe;)
  from being misclassified as per-test callbacks.
- Add RuleTester cases pinning beforeEach, beforeAll, afterEach,
  test.step, aliased test objects, and the describe-alias edge case.
- Re-snapshot: 89 violations (79 + 9 beforeEach + 1 aliased describe),
  suppressions total 94 (unchanged, 4 promoted rules) + 89 = 183.
Flags e2e tests whose body only performs page/locator interactions
(clicks, fills, navigation) with no expect() reference anywhere and no
call to anything beyond the test's own Playwright fixtures - i.e. tests
that are provably assertion-free.

The rule intentionally does not flag delegation to a helper or
page-object method (entity.descriptionUpdate(page), addUser(...),
verifyAuthenticated(...)), since those may assert internally and
proving otherwise needs interprocedural analysis this rule doesn't do.
An earlier text-scan-only design flagged 474 tests, but hand-verifying
samples showed most delegate to helpers that do assert - a large,
confirmed false-positive rate. Scoping the rule to only what it can
prove (no expect text + every call rooted at a page fixture) drops
that to a single, high-confidence violation:
playwright/e2e/nightly/ServiceIngestion.spec.ts's "Default Pagination
size should be 15" test, which waits on two network patterns and
clicks once but never asserts a status, body, or visible count.

Registered as om-playwright/require-assertion-per-test, scoped to
playwright/e2e/**/*.spec.{js,jsx,ts,tsx} only (utils/support files
contain no tests). Snapshot suppresses the one existing violation via
--suppress-rule; the four already-promoted playwright/* suppressions
(94) and om-playwright/no-blanket-test-slow (89) are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e-assertion-per-test

test.slow(true), test.setTimeout(...), and test.step(...) were being
treated as ordinary non-page calls, so any test opening with one of
them (89 suppressed sites use test.slow()) was silently exempted no
matter how purely it clicked afterward - none of them can assert.

A call rooted at the `test` identifier is now filtered out before the
"every remaining call is page/locator-only" check, instead of counting
either way. test.step's own nested calls are unaffected: they're
separate CallExpression nodes checked on their own merits, since the
callback is inline and fully visible, not a delegated helper.

Corpus impact: still exactly 1 violation, same site
(ServiceIngestion.spec.ts:189) - no re-snapshot needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…row helper defaults

Widen no-positional-locator to flag Identifier/MemberExpression receivers
(hoisted-variable and property-stored locators), not just inline call-chain
tails - 185 previously-missed sites, closing the cheapest evasion path
(hoist into a variable). Discriminate genuine Playwright .first()/.last()/
.nth() calls from same-named non-Locator calls (e.g. lodash) by argument
arity instead.

Fix scopedLocators helpers: getRowByName defaulted to a CSS attribute
selector that never matches implicit ARIA roles (Ant Design <tr> rows),
and expectRowFor never forwarded rowSelector. Default to page.getByRole('row')
and thread rowSelector through both helpers; document the hasText
substring-collision failure mode.

Re-snapshot: no-positional-locator suppressions 1068 -> 1253 via
--suppress-rule (never --suppress-all/--prune-suppressions); the three
pre-existing suppression groups are byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e escape hatch

Add no-unscoped-count-assertion and require-unique-entity-name (heuristic,
expect false positives) plus justified-rule-disable, the escape hatch that
lets a developer document why a heuristic misfire is being suppressed.

require-unique-entity-name is implemented, tested, and registered in the
plugin but deliberately not enabled: a raw pass found 1625 violations, and
a 25-sample hand review found 0/25 true positives (matches were almost
entirely Playwright's own getByRole(..., { name }) option and static
config keys, not entity creation). Snapshotting that baseline would ratchet
near-total noise; left for a follow-up to narrow the heuristic. The other
two rules were sanity-checked, found to carry real signal, and are
snapshotted normally (+73, +9 violations; suppressions total 1437 -> 1519).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e-entity-name

Both were reviewed against their full/near-full population, not just the
sanity-check samples from the initial implementation:

- require-unique-entity-name: confirmed ~3/1625 (0.18%) true-positive
  rate. The heuristic can't distinguish assigning a name to a new entity
  from using one to look an existing element up — 59% of hits are
  Playwright's own getByRole/getByLabel(role, { name }) accessible-name
  option, where adding uuid() would break the locator. 43/51 files under
  playwright/support/entity/ already call uuid() internally, so
  uniqueness is solved one layer down from where this rule looked.
  Never enabled, so no suppressions to remove.

- no-unscoped-count-assertion: full classification of all 73 sites found
  ~77% false positives (17 true / 56 false), dominated by the rule
  flagging locators already scoped through a variable per its own
  recommended fix (const c = loc.filter({ hasText }); expect(c)...) —
  failing code for following its own advice. Suppressions pruned via a
  full-corpus --prune-suppressions run; total 1519 -> 1446.

justified-rule-disable is untouched (its count of 9 was independently
re-verified) and now registers as the fifth rule in the plugin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ate guidance

Docs previously listed a hand-written, stale error/warn split for the Playwright
ESLint rules that no longer matched eslint.config.mjs (every rule now runs at
error). Generate the rule table from the config instead, so the docs can't
drift from what CI actually enforces, and consolidate the overlapping rules
file and skills onto the generated table and the real lint:playwright:suppressions gate.
Adds a corpus meta-test that fails if eslint-suppressions.json's total
grows (ceiling 1446, the current baseline), and wires a full-corpus CI
step running the rule unit tests, the suppressions-aware lint with
pruning, and the generated rule-table freshness check. The step is
unconditional rather than changed-files-scoped because
--prune-suppressions against a partial file set would delete valid
suppressions for files the PR didn't touch. Until this lands, all 18
Playwright guardrail rules only ran locally.

Also updates skills/playwright-validation/SKILL.md's stale
three-rule/warn-tier description to reflect that every rule is now
error-level with no warn tier.
…wright guardrail step

--prune-suppressions writes eslint-suppressions.json in place when it
drops a stale entry, and the step never restored it — a later step in
the same job (lint_core_components) runs a repo-wide git status check
that could then misattribute this failure to itself. Restore the file
unconditionally on both the pass and fail paths.

Also guard the failure-detail grep with `|| true`: under GitHub's
default `bash -eo pipefail` shell, a grep with no matches exits
non-zero and would abort the script before the GITHUB_OUTPUT writes,
leaving the PR summary comment blank.
Playwright already runs with retries: 1 and the JSON reporter is already
enabled, so flaky/failed data is produced and discarded on every CI run.
Add a small aggregator that turns results.json into a flake-report.json
summary per shard, uploaded as an artifact. Purely additive: no new test
runtime, no changes to any existing step or job outcome.
…load

Four fixes from final branch review:
- ui-checkstyle: run the non-pruning yarn lint:playwright:full so a fixed
  violation fails CI until its stale suppression is pruned and committed,
  instead of silently discarding the rewrite; drop the now-dead
  git checkout restore; update the failure-reason text accordingly.
- justified-rule-disable rule + CI grep guard: a bare eslint-disable (no
  rule list) or a disable of the rule itself previously bypassed all 18
  guardrail rules undetected; the rule now reports blanket disables and a
  CI-level grep guard catches the self-referential case the rule cannot see.
- playwright-postgresql-e2e: add continue-on-error/overwrite/if-no-files-found
  to the flake-report upload so artifact-name collisions on job re-run don't
  fail an otherwise-passing 40-minute shard.
- docs: point developer-facing lint instructions at
  yarn lint:playwright:suppressions to match the new CI command split.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI's changed-files checkstyle runs prettier, which the guardrail work did not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 5, 2026 12:01
@github-actions

github-actions Bot commented Aug 5, 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 github-actions Bot added safe to test Add this label to run secure Github workflows on PRs UI UI specific issues labels Aug 5, 2026

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.

Pull request overview

This PR continues the Playwright guardrails rollout by reducing positional locator usage across playwright/e2e/Pages/**, and aligning repo guidance/tooling around the new full-corpus lint gate (yarn lint:playwright:suppressions).

Changes:

  • Refactors many Playwright Page specs to avoid .first()/.last()/.nth() by narrowing locators, scoping to stable identifiers, using count assertions, or adding justified disables where position is the semantic.
  • Introduces/updates Playwright guardrail tooling: custom om-playwright ESLint rules + rule tests, generated handbook rule table script, and flake report aggregation.
  • Updates skills/docs/CI configs to reference the suppressions ratchet lint command and generated rule table.

Reviewed changes

Copilot reviewed 88 out of 88 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
skills/writing-playwright-tests/SKILL.md Redirects authorship guidance to the consolidated playwright skill.
skills/test-enforcement/SKILL.md Updates Playwright lint verification command to lint:playwright:suppressions.
skills/playwright/SKILL.md Updates lint gate guidance + merges “Writing Playwright Tests” content into this skill.
skills/playwright-validation/SKILL.md Updates validation checklist and lint command for the new gate.
openmetadata-ui/src/main/resources/ui/scripts/generate-playwright-rule-table.js Generates/validates the handbook’s rule table from eslint.config.mjs.
openmetadata-ui/src/main/resources/ui/scripts/aggregate-flake-report.js Aggregates Playwright JSON reporter output into a flake summary JSON.
openmetadata-ui/src/main/resources/ui/scripts/aggregate-flake-report.test.js Unit tests for flake report aggregation.
openmetadata-ui/src/main/resources/ui/playwright/utils/waitHelpers.ts Adds clickAndWaitFor helper to avoid waitForResponse-after-action races.
openmetadata-ui/src/main/resources/ui/playwright/utils/scopedLocators.ts Adds getRowByName / expectRowFor helpers to avoid position-based row selection.
openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts Registers response listeners before actions in user helper flows.
openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts Registers response listeners before actions in glossary helper flows.
openmetadata-ui/src/main/resources/ui/playwright/utils/explore.ts Removes an early waitForResponse call before clicking a filter.
openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts Ensures response listener is registered before triggering search.
openmetadata-ui/src/main/resources/ui/playwright/PLAYWRIGHT_DEVELOPER_HANDBOOK.md Updates lint command, documents ratchet behavior, and replaces rule table with generated section.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/index.js Exports local om-playwright ESLint rules.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/no-positional-locator.js Implements rule banning .first()/.last()/.nth() locator calls.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/no-blanket-test-slow.js Implements rule banning test.slow() at file/describe/beforeEach scope.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/require-assertion-per-test.js Implements rule requiring at least one assertion in e2e tests (conservative detection).
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/require-response-listener-before-action.js Implements rule disallowing await page.waitForResponse(...) patterns.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/justified-rule-disable.js Enforces -- <why> justification on disabling Playwright rules and blocks blanket disables.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/smoke.test.js Plugin export smoke test.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/no-positional-locator.test.js RuleTester suite for positional-locator rule.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/no-blanket-test-slow.test.js RuleTester suite for blanket test.slow rule.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/require-assertion-per-test.test.js RuleTester suite for assertion-per-test rule.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/require-response-listener-before-action.test.js RuleTester suite for response-listener ordering rule.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/justified-rule-disable.test.js RuleTester + pinned Linter behavior test for disable comment self-suppression.
openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.js Pins suppression-count ceiling to enforce monotonic “baseline only shrinks”.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts Removes positional locators and scopes persona/avatar selection; converts some existence waits.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts Replaces positional locators with scoped lookups and count-based waits; adds justified disables where needed.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuiteDetailsPage.spec.ts Switches modal checkbox selection to a known test-case test id.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Teams.spec.ts Removes .first() from switch locator to rely on strictness/scoping.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TeamAssetsRightPanel.spec.ts Removes .first() from right-panel link locator.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts Removes positional locators when opening task cards and verifying task presence.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskFormSettings.spec.ts Removes .first() from AntD option selection chain.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskComments.spec.ts Removes .first() from task-card locator.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts Converts loader waits to count assertions; adds justified positional disable where duplicates are known.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TagPageRightPanel.spec.ts Removes .first() from right-panel link locator.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tag.spec.ts Converts loader waits to count assertions; removes .first() from classification entry selection.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceListing.spec.ts Removes .first() from visibility assertion.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts Keeps .first() with justified disables where “first configured field” is intentional.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchIndexApplication.spec.ts Converts “wait for first card visible” to count assertions.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Roles.spec.ts Removes positional selection and narrows delete button locator.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ProfilerConfigurationPage.spec.ts Converts initial row wait to count assertion; uses justified .first() while draining rows.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts Replaces positional selectors with scoped rule-card targeting; adds justified positional disables where position is semantic.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ODCSImportExport.spec.ts Removes .first() from description/markdown locators.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LogsViewer.spec.ts Removes .first() from logs button locators.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LiveIndexingTab.spec.ts Converts .first()-based “exists” checks to count assertions.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts Moves waitForResponse before click dispatch events.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/LineageInteraction.spec.ts Removes .first() from close button; keeps .nth(i) with justified disable for iteration.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts Replaces positional locators with better scoping; keeps justified positional uses where UI structure requires it.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/InputOutputPorts.spec.ts Replaces positional count checks with helper-based assertions.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryTermRightPanel.spec.ts Removes .first() from right-panel link locator.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryTermRelationSettings.spec.ts Removes .first() from Next Page button role lookup.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryImportExport.spec.ts Adds justified positional disables for overlays/grid cell order; removes some .first() usages.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryFormValidation.spec.ts Removes .first() from form-error selection; scopes duplicate-name error lookup.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts Removes .first() where safe; adds justified positional disables where ordering is the assertion.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ExploreTree.spec.ts Narrows tree switcher click to a stable node container; adds justified positional disable for tag breadcrumb selection.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ExplorePageRightPanel.spec.ts Converts “first visible” waits to counts; adds justified positional disables for representative picks.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ExploreBrowse.spec.ts Scopes breadcrumb collapse button to a specific result card.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Entity.spec.ts Replaces positional existence checks with count assertions; adds justified disables where UI has indistinguishable prev/next buttons, etc.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DomainUIInteractions.spec.ts Removes .first() in many places; keeps justified positional selection where the UI reuses ids/testids.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts Scopes combobox selection and removes .nth(2) grid-cell clicks in domain tree view test.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DomainDataProductsRightPanel.spec.ts Removes .first() from right-panel link locator.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DomainAdvanced.spec.ts Converts waits to response-first ordering; removes .first() where safe.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProducts.spec.ts Converts .first()-based card existence check to count assertion; removes .first() on listbox hidden wait.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProductAndSubdomains.spec.ts Moves response listeners before action; adds justified positional disables where duplicate rendering is known.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplaceAnnouncements.spec.ts Replaces .first().waitFor() with a count assertion.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts Adds justified positional disables for nondeterministic “recent items” widgets.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataInsight.spec.ts Registers waitForResponse before sidebar navigation.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts Scopes query-builder selectors to active card and reduces global .nth() reliance; keeps justified positional for append-last semantics.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts Keeps “any owner” selection as justified .first(); reduces .nth(0) usage.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts Keeps justified positional disable for CodeMirror line placement; removes unnecessary .nth(0).
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CSVImportWithQuotesAndCommas.spec.ts Removes .last() from dropdown container selection and simplifies gridcell assertions.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ClassificationConditionalRendering.spec.ts Converts .first() visibility checks to count assertions.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/AuditLogs.spec.ts Scopes dropdown option selection; converts skeleton waits to count assertions; adds justified positional disables where selection is intentionally arbitrary.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/AppRunsHistoryLogs.spec.ts Removes .first() from logs button locators.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Table.spec.ts Moves response listeners before save click.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts Moves roles search listener before typing filters.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/LargeGlossaryPerformance.spec.ts Moves glossary term search listeners before typing/clearing.
openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/EntitySummaryPanel.spec.ts Adds response listener before navigating to Explore.
openmetadata-ui/src/main/resources/ui/package.json Adds scripts for suppressions ratchet lint, rule tests, generated handbook table, and flake report.
openmetadata-ui/src/main/resources/ui/eslint.config.mjs Wires om-playwright plugin and promotes Playwright rules to error under suppressions ratchet.
openmetadata-ui/src/main/resources/ui/.prettierignore Ignores eslint-suppressions.json to avoid formatter churn vs ESLint’s format.
.github/workflows/ui-checkstyle.yml Adds full-corpus Playwright guardrails job (rule tests + lint + stale-table check) and bypass guards.
.github/workflows/playwright-postgresql-e2e.yml Uploads aggregated flake report artifact per shard.
.claude/rules/frontend-playwright.md Updates Playwright constraints doc to reflect the single lint gate and new enforced rules.
Suppressed comments (1)

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts:141

  • Same strict-mode issue as above: page.locator('[data-testid="task-feed-card"]') can match multiple cards, and click() will throw. If the rejection flow is meant to open the first available task, keep .first() with a justified disable, or otherwise uniquely scope the card locator.

Comment on lines 124 to 129
const resolveTaskWithApproval = async (page: Page) => {
// Click on the first task card to open it
const taskCard = page.locator('[data-testid="task-feed-card"]').first();
const taskCard = page.locator('[data-testid="task-feed-card"]');
if (await taskCard.isVisible()) {
await taskCard.click();
await waitForPageLoaded(page);
Comment on lines +21 to +36
export const clickAndWaitFor = async (
page: Page,
locator: Locator,
urlPattern: string | RegExp,
expectedStatus = 200
): Promise<Response> => {
const responsePromise = page.waitForResponse(urlPattern);
await locator.click();
const response = await responsePromise;

if (response.status() !== expectedStatus) {
throw new Error(
`Expected ${String(
urlPattern
)} to return ${expectedStatus}, got ${response.status()}`
);
Comment on lines 825 to 828
### ESLint
- [ ] `yarn lint:playwright` passes with zero errors
- [ ] `yarn lint:playwright:suppressions` passes with zero errors
- [ ] No new warnings introduced (fix existing ones when touching a file)

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 65%
66.01% (77627/117595) 49.97% (46837/93726) 51.15% (14081/27526)

ShaileshParmar11 and others added 2 commits August 5, 2026 17:48
CodeQL flagged the separate guardrail step as a high-severity cache-poisoning
risk: it executes PR-authored code (the eslint config and the local rule
plugin) in a pull_request_target job with a privileged checkout.

The file already documents the convention - every `run:` executing PR code in
this job is a poisonable step, so a separate step adds one more for no gain in
isolation. Folding the guardrail commands into the existing playwright step
removes the new alert without weakening the gate.

The checks now run before --fix/--write so they judge what the contributor
actually wrote, and the changed-files matcher is widened to eslint.config.mjs
and eslint-suppressions.json so a config-only change still triggers the gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tes)

Suppressions 1446 -> 1146; no-positional-locator 1265 -> 953.

Notable root cause resolved rather than worked around: in the query-builder
specs, `.group` matches two elements per instance (RuleGroup wraps itself in
Draggable("group rule_group")) and ExpandableCard never unmounts saved
semantics, so global counts grew as a test added more. Scoping to
`.expanded-active-card .rule_group` removes the need for page-wide .nth().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

@ShaileshParmar11

Copy link
Copy Markdown
Contributor Author

Closing until the 2.0 release is out — parking this rather than abandoning it. Will reopen once the release lands.

State when closed: ready for review. The branch claude/playwright-positional-pages is preserved at 3ab6799f06; nothing here is lost by closing.

On reopen, in this order:

  1. Reopen and merge Fixes 31036: Playwright guardrails — enforce test anti-patterns in CI via ESLint + suppressions ratchet #31026 (the gate) first — this PR is branched from it and is meaningless without it.
  2. Rebase this branch onto main.
  3. Re-run the full-corpus prune (yarn lint:playwright:suppressions in openmetadata-ui/src/main/resources/ui) and commit the pruned eslint-suppressions.json.
  4. Update PW_SUPPRESSION_CEILING in playwright/eslint-rules/tests/corpus.test.js to the new total.

Note these sibling PRs all touch eslint-suppressions.json, so they can be reviewed in parallel but must merge sequentially, each rebasing and re-pruning after the previous.

@gitar-bot

gitar-bot Bot commented Aug 13, 2026

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

Burns down 312 positional locator usages across 48 Playwright e2e page files by replacing them with scoped selectors, identity attributes, and count assertions, reducing the suppression ceiling to 1,146. No issues found.

✅ 1 resolved
Quality: DataContractsSemanticRules left on unscoped .group unlike DataContracts

📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts:101 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts:198 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts:302 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts:253 📄 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts:1033
In DataContractsSemanticRules.spec.ts the fix merely drops .nth(0) leaving page.locator('.group'), whereas DataContracts.spec.ts scopes the same query-builder node to .expanded-active-card .rule_group. Per the PR's own analysis .group matches two elements per RuleGroup instance and the global count grows as semantics stay mounted; the unscoped locator here only works because the subsequent ruleLocator.locator('.group--field ...') chain dedups to a single descendant for a single active semantic. If any of these tests ever mount more than one semantic card, the locator will resolve multiple .group--field nodes and fail under strict mode. Consider applying the same .expanded-active-card .rule_group scoping used in DataContracts.spec.ts for consistency and robustness.

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 UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants