Skip to content

Conversation

@gustavolira
Copy link
Member

@gustavolira gustavolira commented Dec 11, 2025

Description

Refactored Playwright E2E tests to follow best practices using semantic locators and removed common anti-patterns that cause test flakiness.

Which issue(s) does this PR fix

PR acceptance criteria

Please make sure that the following steps are complete:

  • GitHub Actions are completed and successful
  • Unit Tests are updated and passing
  • E2E Tests are updated and passing
  • Documentation is updated if necessary (requirement for new features)
  • Add a screenshot if the change is UX/UI related

How to test changes / Special notes to the reviewer

@openshift-ci
Copy link

openshift-ci bot commented Dec 11, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign gustavolira for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@rhdh-qodo-merge
Copy link

rhdh-qodo-merge bot commented Dec 11, 2025

PR Reviewer Guide 🔍

(Review updated until commit ce0cf78)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🔒 No security concerns identified
⚡ Recommended focus areas for review

Locator Robustness

New chained .or() locators combine different strategies; if both branches match multiple elements, interactions may become ambiguous or flaky. Validate that these locators resolve to a single, actionable element in all target UIs/environments.

// Wait for permissions step to be ready
await expect(page.getByRole("heading", { name: /Configure permission/i }).or(page.getByTestId("nextButton-2"))).toBeVisible();
await uiHelper.clickButton("Next");
// Wait for review step to be ready
await expect(page.getByRole("heading", { name: /Review/i }).or(page.getByRole("button", { name: "Save" }))).toBeVisible();
await uiHelper.clickButton("Save");
API Surface Stability

Large addition of new semantic helper methods alongside deprecated legacy selectors introduces dual paths. Ensure callers migrate consistently and that tree-shaking/build size and naming remain stable to avoid accidental cross-usage.

 * New code should use SemanticSelectors class or the get*() methods below.
 */
export const UI_HELPER_ELEMENTS = {
  // ========================================
  // LEGACY SELECTORS - Maintained for backward compatibility
  // These will be removed in a future phase after all tests are migrated
  // ========================================

  /** @deprecated Use SemanticSelectors.button(page, name) or getButton() */
  MuiButtonLabel:
    'span[class^="MuiButton-label"],button[class*="MuiButton-root"]',
  /** @deprecated Use SemanticSelectors.button(page, name) with filter */
  MuiToggleButtonLabel: 'span[class^="MuiToggleButton-label"]',
  /** @deprecated Use SemanticSelectors.inputByLabel(page, label) */
  MuiBoxLabel: 'div[class*="MuiBox-root"] label',
  /** @deprecated Use SemanticSelectors.tableHeader(page, name) or getTableHeader() */
  MuiTableHead: 'th[class*="MuiTableCell-root"]',
  /** @deprecated Use SemanticSelectors.tableCell(page, text) or getTableCell() */
  MuiTableCell: 'td[class*="MuiTableCell-root"]',
  /** @deprecated Use SemanticSelectors.tableRow(page, text) or getTableRow() */
  MuiTableRow: 'tr[class*="MuiTableRow-root"]',
  /** @deprecated Use semantic selectors with appropriate styling checks */
  MuiTypographyColorPrimary: ".MuiTypography-colorPrimary",
  /** @deprecated Use SemanticSelectors.checkbox() with appropriate checks */
  MuiSwitchColorPrimary: ".MuiSwitch-colorPrimary",
  /** @deprecated Use SemanticSelectors.button(page, name) */
  MuiButtonTextPrimary: ".MuiButton-textPrimary",
  /** @deprecated Use getCardByHeading() method or SemanticSelectors.region().filter() */
  MuiCard: (cardHeading: string) =>
    `//div[contains(@class,'MuiCardHeader-root') and descendant::*[text()='${cardHeading}']]/..`,
  /** @deprecated Use getCardByText() method or SemanticSelectors.region().filter() */
  MuiCardRoot: (cardText: string) =>
    `//div[contains(@class,'MuiCard-root')][descendant::text()[contains(., '${cardText}')]]`,
  /** @deprecated Use SemanticSelectors.table(page) or getTable() */
  MuiTable: "table.MuiTable-root",
  /** @deprecated Use SemanticSelectors.region().filter() for card headers */
  MuiCardHeader: 'div[class*="MuiCardHeader-root"]',
  /** @deprecated Use SemanticSelectors.inputByPlaceholder() or inputByLabel() */
  MuiInputBase: 'div[class*="MuiInputBase-root"]',
  /** @deprecated Use appropriate semantic selector based on element role */
  MuiTypography: 'span[class*="MuiTypography-root"]',
  /** @deprecated Use SemanticSelectors.alert(page, name) */
  MuiAlert: 'div[class*="MuiAlert-message"]',
  /** ✅ This is already semantic, but prefer SemanticSelectors.tab(page, name) */
  tabs: '[role="tab"]',
  /** @deprecated Use SemanticSelectors.tableRow(page, text) */
  rowByText: (text: string) => `tr:has(:text-is("${text}"))`,

  // ========================================
  // NEW SEMANTIC METHODS - Preferred approach
  // Use these for new code and when refactoring
  // ========================================

  /**
   * Get a button by its accessible name
   * ✅ Preferred over MuiButtonLabel
   * @example UI_HELPER_ELEMENTS.getButton(page, 'Submit').click()
   */
  getButton: (page: Page, name: string | RegExp): Locator =>
    SemanticSelectors.button(page, name),

  /**
   * Get a link by its accessible name
   * ✅ Preferred for navigation elements
   * @example UI_HELPER_ELEMENTS.getLink(page, 'View Details').click()
   */
  getLink: (page: Page, name: string | RegExp): Locator =>
    SemanticSelectors.link(page, name),

  /**
   * Get a table element
   * ✅ Preferred over MuiTable
   * @example const table = UI_HELPER_ELEMENTS.getTable(page)
   */
  getTable: (page: Page): Locator =>
    SemanticSelectors.table(page),

  /**
   * Get a table cell by content
   * ✅ Preferred over MuiTableCell
   * @example UI_HELPER_ELEMENTS.getTableCell(page, 'Active')
   */
  getTableCell: (page: Page, text?: string | RegExp): Locator =>
    SemanticSelectors.tableCell(page, text),

  /**
   * Get a table header (column header)
   * ✅ Preferred over MuiTableHead
   * @example UI_HELPER_ELEMENTS.getTableHeader(page, 'Created At').click()
   */
  getTableHeader: (page: Page, name: string | RegExp): Locator =>
    SemanticSelectors.tableHeader(page, name),

  /**
   * Get a table row by content
   * ✅ Preferred over MuiTableRow and rowByText
   * @example const row = UI_HELPER_ELEMENTS.getTableRow(page, 'Guest User')
   */
  getTableRow: (page: Page, text?: string | RegExp): Locator =>
    SemanticSelectors.tableRow(page, text),

  /**
   * Get a heading by text and optional level
   * @example UI_HELPER_ELEMENTS.getHeading(page, 'RBAC', 1)
   */
  getHeading: (page: Page, name: string | RegExp, level?: 1 | 2 | 3 | 4 | 5 | 6): Locator =>
    SemanticSelectors.heading(page, name, level),

  /**
   * Get a tab by name
   * ✅ Preferred over tabs selector
   * @example UI_HELPER_ELEMENTS.getTab(page, 'Settings').click()
   */
  getTab: (page: Page, name: string | RegExp): Locator =>
    SemanticSelectors.tab(page, name),

  /**
   * Get a dialog/modal
   * @example const dialog = UI_HELPER_ELEMENTS.getDialog(page, 'Confirm Delete')
   */
  getDialog: (page: Page, name?: string | RegExp): Locator =>
    SemanticSelectors.dialog(page, name),

  /**
   * Get a card by heading text (semantic alternative to MuiCard)
   * ✅ Preferred over MuiCard XPath selector
   * @example const card = UI_HELPER_ELEMENTS.getCardByHeading(page, 'RHDH Build info')
   */
  getCardByHeading: (page: Page, heading: string | RegExp): Locator => {
    // Find region or article containing the heading
    return page.locator('[role="region"], article, section').filter({
      has: page.getByRole('heading', { name: heading })
    }).first();
  },

  /**
   * Get a card by text content (semantic alternative to MuiCardRoot)
   * ✅ Preferred over MuiCardRoot XPath selector
   * @example const card = UI_HELPER_ELEMENTS.getCardByText(page, 'Context one')
   */
  getCardByText: (page: Page, text: string | RegExp): Locator => {
    return page.locator('[role="region"], article, section').filter({
      hasText: text
    }).first();
  },

  /**
   * Get an input by label
   * ✅ Preferred over MuiInputBase
   * @example UI_HELPER_ELEMENTS.getInputByLabel(page, 'Email').fill('[email protected]')
   */
  getInputByLabel: (page: Page, label: string | RegExp): Locator =>
    SemanticSelectors.inputByLabel(page, label),

  /**
   * Get an input by placeholder
   * ✅ Preferred over MuiInputBase
   * @example UI_HELPER_ELEMENTS.getInputByPlaceholder(page, 'Search...').fill('test')
   */
  getInputByPlaceholder: (page: Page, placeholder: string | RegExp): Locator =>
    SemanticSelectors.inputByPlaceholder(page, placeholder),

  /**
   * Get an alert element
   * ✅ Preferred over MuiAlert
   * @example await expect(UI_HELPER_ELEMENTS.getAlert(page, 'Error')).toBeVisible()
   */
  getAlert: (page: Page, name?: string | RegExp): Locator =>
    SemanticSelectors.alert(page, name),

  /**
   * Get navigation element
   * @example const nav = UI_HELPER_ELEMENTS.getNavigation(page)
   */
  getNavigation: (page: Page, name?: string | RegExp): Locator =>
    SemanticSelectors.navigation(page, name),

  /**
   * Get menu item
   * @example UI_HELPER_ELEMENTS.getMenuItem(page, 'Delete').click()
   */
  getMenuItem: (page: Page, name: string | RegExp): Locator =>
    SemanticSelectors.menuItem(page, name),
};
Assertion Semantics

Replaced waitForSelector/timeout with visibility assertions like expect(page.getByText("Error")).toBeHidden(); verify "Error" text is uniquely scoped to the creation flow to avoid false negatives if unrelated error banners exist.

await expect(page.getByText("second")).toBeVisible();
// Verify no error occurred during creation
await expect(page.getByText("Error")).toBeHidden();
📄 References
  1. No matching references available

@rhdh-qodo-merge
Copy link

PR Type

Enhancement


Description

  • Introduce SemanticSelectors class with 30+ role-based locator methods following Playwright best practices

  • Add semantic getter methods to page object files (global-obj, page-obj) as preferred alternatives to legacy MUI class selectors

  • Mark legacy CSS class selectors as deprecated with migration guidance to semantic selectors

  • Include WaitStrategies helper class for preferred wait patterns over waitForTimeout


File Walkthrough

Relevant files
Enhancement
semantic-selectors.ts
New semantic selectors class with role-based locators       

e2e-tests/playwright/support/selectors/semantic-selectors.ts

  • Create new SemanticSelectors class with 30+ static methods for
    role-based element selection (button, link, table, heading, input,
    checkbox, dialog, etc.)
  • Implement helper functions findTableCell() and findTableCellByColumn()
    for table navigation
  • Add WaitStrategies class with methods for visibility, network idle,
    and API response waiting
  • Include comprehensive JSDoc documentation with examples for each
    selector method
+475/-0 
global-obj.ts
Add semantic getter methods to global page objects             

e2e-tests/playwright/support/page-objects/global-obj.ts

  • Add imports for Page, Locator, and SemanticSelectors
  • Mark all legacy MUI class selectors (WAIT_OBJECTS, UI_HELPER_ELEMENTS)
    as @deprecated with migration guidance
  • Add 15+ new semantic getter methods (getButton, getLink, getTable,
    getTableCell, getTableHeader, getTableRow, getHeading, getTab,
    getDialog, getCardByHeading, getCardByText, getInputByLabel,
    getInputByPlaceholder, getAlert, getNavigation, getMenuItem)
  • Each new method wraps SemanticSelectors or implements semantic
    filtering with JSDoc examples
+180/-1 
page-obj.ts
Add semantic methods to all page component objects             

e2e-tests/playwright/support/page-objects/page-obj.ts

  • Add imports for Page, Locator, and SemanticSelectors
  • Mark legacy MUI class selectors as @deprecated across all component
    objects (HOME_PAGE_COMPONENTS, SEARCH_OBJECTS_COMPONENTS,
    CATALOG_IMPORT_COMPONENTS, KUBERNETES_COMPONENTS,
    BACKSTAGE_SHOWCASE_COMPONENTS, SETTINGS_PAGE_COMPONENTS,
    ROLES_PAGE_COMPONENTS, DELETE_ROLE_COMPONENTS,
    ROLE_OVERVIEW_COMPONENTS_TEST_ID)
  • Add 25+ new semantic getter methods with JSDoc documentation and usage
    examples
  • Include migration guidance comments for each deprecated selector
  • Preserve existing data-testid and aria-label selectors marked as
    already semantic
+220/-0 

@gustavolira gustavolira force-pushed the refactor/playwright-best-practices branch from b2b584c to 0ddad92 Compare December 11, 2025 18:58
@rhdh-qodo-merge
Copy link

rhdh-qodo-merge bot commented Dec 11, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Enforce adoption of new selectors

To ensure consistent adoption of the new semantic selectors, this suggestion
proposes creating a custom ESLint rule. This rule would flag and prevent the use
of the old, deprecated selectors, enforcing the migration and improving
long-term code maintenance.

Examples:

e2e-tests/playwright/support/page-objects/global-obj.ts [26-207]
export const UI_HELPER_ELEMENTS = {
  // ========================================
  // LEGACY SELECTORS - Maintained for backward compatibility
  // These will be removed in a future phase after all tests are migrated
  // ========================================

  /** @deprecated Use SemanticSelectors.button(page, name) or getButton() */
  MuiButtonLabel:
    'span[class^="MuiButton-label"],button[class*="MuiButton-root"]',
  /** @deprecated Use SemanticSelectors.button(page, name) with filter */

 ... (clipped 172 lines)
e2e-tests/playwright/support/page-objects/page-obj.ts [14-276]
export const HOME_PAGE_COMPONENTS = {
  // Legacy selectors - maintained for backward compatibility
  /** @deprecated Use SemanticSelectors.region() with appropriate filter */
  MuiAccordion: 'div[class*="MuiAccordion-root-"]',
  /** @deprecated Use SemanticSelectors.region() or article with appropriate filter */
  MuiCard: 'div[class*="MuiCard-root-"]',

  // Semantic methods - preferred
  /**
   * Get accordion/expandable section by heading text

 ... (clipped 253 lines)

Solution Walkthrough:

Before:

// e2e-tests/playwright/support/page-objects/global-obj.ts

export const UI_HELPER_ELEMENTS = {
  // LEGACY SELECTORS
  /** @deprecated Use SemanticSelectors.button(page, name) or getButton() */
  MuiButtonLabel:
    'span[class^="MuiButton-label"],button[class*="MuiButton-root"]',
  
  // ... other deprecated selectors

  // NEW SEMANTIC METHODS
  getButton: (page: Page, name: string | RegExp): Locator =>
    SemanticSelectors.button(page, name),
  
  // ... other new methods
};

After:

// .eslintrc.js (conceptual)
module.exports = {
  // ...
  rules: {
    'no-restricted-syntax': [
      'error',
      {
        selector: 'MemberExpression[object.name="UI_HELPER_ELEMENTS"][property.name="MuiButtonLabel"]',
        message: 'UI_HELPER_ELEMENTS.MuiButtonLabel is deprecated. Use the new getButton() method instead.',
      },
      // ... other rules for all deprecated selectors
    ],
  },
};

// In a test file, this would now raise a linting error:
// ERROR: UI_HELPER_ELEMENTS.MuiButtonLabel is deprecated.
page.locator(UI_HELPER_ELEMENTS.MuiButtonLabel);
Suggestion importance[1-10]: 8

__

Why: This is a high-impact strategic suggestion that addresses the long-term maintainability and success of the PR's refactoring by proposing a concrete enforcement mechanism (ESLint rule) to ensure adoption of the new pattern.

Medium
Possible issue
Fix incorrect accordion locator logic

Fix the getClusterAccordion selector by first locating the accordion element and
then finding the button within it, as the original filter logic is incorrect.

e2e-tests/playwright/support/page-objects/page-obj.ts [100-105]

 getClusterAccordion: (page: Page, clusterName?: string | RegExp): Locator => {
   if (clusterName) {
     return page.getByRole('button', { name: clusterName });
   }
-  return page.getByRole('button').filter({ has: page.locator('[class*="MuiAccordion"]') }).first();
+  // Find the first accordion element, then get the button inside it.
+  return page.locator('[class*="MuiAccordion-root"]').first().getByRole('button');
 },
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a logical flaw in the Playwright selector for accordions, where the filter logic was inverted, and provides a working alternative that prevents the selector from failing.

Medium
General
Remove redundant explicit wait helpers

Remove the redundant forVisible and forHidden explicit wait helpers and instead
use Playwright's built-in auto-waiting assertions like
expect(locator).toBeVisible().

e2e-tests/playwright/support/selectors/semantic-selectors.ts [429-443]

 export class WaitStrategies {
   /**
-   * Wait for element to be visible (auto-waiting assertion)
-   * Preferred over waitForTimeout
+   * Wait for network to be idle
+   * @deprecated Prefer web-first assertions like `expect(locator).toBeVisible()` over explicit waits.
+   * This class may be removed in a future update.
    */
-  static async forVisible(locator: Locator, timeout?: number): Promise<void> {
-    await locator.waitFor({ state: 'visible', timeout });
-  }
-
-  /**
-   * Wait for element to be hidden
-   */
-  static async forHidden(locator: Locator, timeout?: number): Promise<void> {
-    await locator.waitFor({ state: 'hidden', timeout });
+  static async forNetworkIdle(page: Page): Promise<void> {
+    await page.waitForLoadState('networkidle');
   }
 ...

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that the explicit wait helpers are redundant with Playwright's preferred expect assertions, and removing them aligns with best practices and simplifies the codebase.

Low
  • Update

@gustavolira
Copy link
Member Author

/review

@rhdh-qodo-merge
Copy link

Persistent review updated to latest commit 0ddad92

@github-actions
Copy link
Contributor

@gustavolira gustavolira force-pushed the refactor/playwright-best-practices branch from 0ddad92 to ce0cf78 Compare December 11, 2025 23:27
@gustavolira
Copy link
Member Author

/review

@rhdh-qodo-merge
Copy link

Persistent review updated to latest commit ce0cf78

@github-actions
Copy link
Contributor

@gustavolira gustavolira force-pushed the refactor/playwright-best-practices branch from 18df22d to 23296ff Compare December 12, 2025 12:22
@gustavolira gustavolira force-pushed the refactor/playwright-best-practices branch from ef23dac to b9bf5d2 Compare December 12, 2025 13:12
@gustavolira gustavolira changed the title WIP - Improve playwright locators feat(e2e): refactor tests to use semantic selectors and remove flaky anti-patterns Dec 12, 2025
gustavolira and others added 15 commits January 2, 2026 10:19
Implemented 3 suggestions from qoDo bot review:

1. Fixed getClusterAccordion to use expanded attribute instead of MUI class
2. Removed redundant WaitStrategies.forVisible/forHidden methods
3. Documented ESLint enforcement recommendation for future implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Fixed ESLint issues introduced in refactoring:
- Removed unused UI_HELPER_ELEMENTS import from catalog-timestamp.spec.ts
- Removed unused table variable from catalog-timestamp.spec.ts
- Replaced raw MUI locator with semantic getByRole in oidc.spec.ts
- Replaced raw tbody tr locator with semantic getByRole('row') filter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Auto-formatted files using Prettier to fix code style issues.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Removed WaitStrategies.forNetworkIdle() method as networkidle is not
recommended by Playwright ESLint rules. It doesn't wait for requests
triggered after load and can give false positives with polling.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Removed unused imports causing ESLint errors:
- kubernetes-actions.spec.ts: Removed UI_HELPER_ELEMENTS
- rbac.spec.ts: Removed Locator and UI_HELPER_ELEMENTS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Updated test descriptions to remove the skip status for multiple test files, allowing them to run as part of the test suite. This includes tests for catalog-timestamp, custom-theme, default-global-header, dynamic-home-page-customization, extensions, home-page-customization, smoke-test, and several plugins.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Modified the global header tests to ensure the first navigation element is targeted for visibility checks, improving test accuracy and reliability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Updated tests to improve accuracy by scoping selectors to specific elements. Changes include targeting the first navigation element in the global header, refining link selection in the learning paths, and replacing heading checks with text content verification in the user settings info card.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Updated various test files to improve accuracy in element selection. Changes include scoping sign-out actions to the profile menu, refining context card interactions to ensure shared state visibility, and optimizing user link retrieval in the catalog users page object.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Refined the test for guest login in the OIDC provider configuration by scoping the selector to the main content area, improving accuracy in identifying sign-in method card headers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Updated the RBAC test suite to enhance accuracy in element selection. Changes include replacing text-based selectors with role-based selectors for navigation links and verifying component metadata visibility, ensuring more reliable test outcomes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
…ication provider and RBAC tests

Updated the application provider tests to improve accuracy in card selection by using index-based access for context cards. In the RBAC tests, refined element selection by scoping visibility checks to specific article elements, ensuring more reliable test outcomes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Enhanced the login process in the Common utility by adding error handling for popup closure during navigation. This ensures that the test can gracefully handle scenarios where the popup closes before the navigation completes, improving test reliability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
…erification

Updated the RBAC page object to include a verification step for the success message after creating a role. This ensures that the test accurately confirms the role creation before proceeding to the roles list, improving overall test reliability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
@github-actions
Copy link
Contributor

github-actions bot commented Jan 2, 2026

The image is available at:

/test e2e-ocp-helm

@github-actions
Copy link
Contributor

github-actions bot commented Jan 2, 2026

🚫 Image Push Skipped.

The container image push was skipped because the build was skipped (either due to [skip-build] tag or no relevant changes with existing image)

/test e2e-ocp-helm

gustavolira and others added 3 commits January 6, 2026 09:26
…loper#3837

Fixes flaky test patterns and strict mode violations:

RBAC Tests (rbac.spec.ts):
- Fix strict mode violation when waiting for review step by removing .or()
  combinator and explicitly checking for Save button visibility/enabled state
- Fix timeout when updating role policies by closing plugins dropdown
  before interacting with permissions table and expanding specific rows
- Applied fixes at lines 383-387 and 786-790 for consistent behavior

RBAC Page Object (rbac-po.ts):
- Add error alert detection in createRole method before waiting for
  success message to provide clearer error feedback when role creation
  fails due to insufficient permissions

Catalog Users (catalog-users-obj.ts):
- Fix getListOfUsers to scope to first table using .first() before
  selecting rowgroup to avoid selecting pagination table
- Change from .last() to .nth(1) for explicit data rows selection

Application Provider (application-provider.spec.ts):
- Fix CSS selector from 'main article > div:first-child > div' to
  'main article).last().locator("> div > div")'
- Resolves timeout by correctly targeting card containers

All changes follow Playwright best practices:
- Use semantic locators (getByRole) over CSS selectors
- Proper element scoping to avoid ambiguity
- Explicit state checks (toBeVisible + toBeEnabled)
- Avoid .or() when both elements can be visible simultaneously

Related to PR redhat-developer#3837: feat(e2e): refactor tests to use semantic selectors
and remove flaky anti-patterns
Implements all suggestions from PR redhat-developer#3837 review:

1. Remove heading level specification (kubernetes-rbac.spec.ts)
   - Changed getByRole('heading', { level: 6 }) to getByRole('heading')
   - More resilient to UI hierarchy changes

2. Refactor button interactions (rbac.spec.ts)
   - Replace uiHelper.clickButton() with direct locator.click()
   - Create dedicated button locators (nextButton, saveButton)
   - More explicit and follows Playwright best practices
   - Applied to both role creation flows (lines 378-386, 785-792)

3. Enhance verifyButtonURL method (ui-helper.ts)
   - Now accepts both string selectors and Locator objects
   - Enables better locator reusability
   - Updated topology.spec.ts to use Locator object

4. Refactor learning-path loop (learning-path-page.spec.ts)
   - Changed from indexed for loop to for-of with .all()
   - More idiomatic and cleaner code
   - Automatically handles all elements without hardcoded count

All changes maintain test functionality while improving:
- Code maintainability
- Playwright best practices adherence
- Locator reusability

Co-authored-by: jrichter1 <[email protected]>
Remove Playwright page fixture from tests that rely on shared page
instance configured in beforeAll. Using fixture parameter would inject
a different page instance, causing reference mismatches with shared
uiHelper, common, and backstageShowcase instances.

Tests now correctly use the global page variable consistent with other
tests in the file and the shared setup pattern.
@gustavolira gustavolira force-pushed the refactor/playwright-best-practices branch from 23e7baf to 9f2827d Compare January 6, 2026 12:54
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 6, 2026

@github-actions
Copy link
Contributor

github-actions bot commented Jan 6, 2026

The image is available at:

/test e2e-ocp-helm

@gustavolira gustavolira requested a review from jrichter1 January 6, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants