Skip to content

Updates for PF6 unit tests#126

Merged
mshriver merged 5 commits into
RedHatQE:mainfrom
mshriver:pf6-test-fixes-hatch
Jul 21, 2026
Merged

Updates for PF6 unit tests#126
mshriver merged 5 commits into
RedHatQE:mainfrom
mshriver:pf6-test-fixes-hatch

Conversation

@mshriver

@mshriver mshriver commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary by Sourcery

Integrate Hatch-based test environments and Playwright browser setup into CI while updating widgets and tests for PatternFly 6 compatibility.

New Features:

  • Add Hatch-managed test environment with Playwright install scripts and pytest convenience commands for PF5 and PF6 variants.

Enhancements:

  • Switch GitHub Actions test workflow to use Hatch for environment creation and test execution, with caching for faster runs.
  • Update dropdown, menu, select, slider, navigation, chip, and radio widgets to use more reliable locators, event dispatching, and wait-based interactions compatible with PatternFly 6.
  • Scope alert and dropdown test locators to stable PatternFly sections and menu toggle markup to avoid PF6-specific flakiness.
  • Set a default timeout on the shared Playwright browser context to reduce hanging tests.

Build:

  • Include Playwright in dev dependencies and configure Hatch environments for testing and linting with associated scripts.

Tests:

  • Adjust menu dropdown disabled and alert tests to use PF6-compatible locators and view roots, improving reliability across PatternFly versions.

Copilot AI review requested due to automatic review settings June 3, 2026 09:23
@sourcery-ai

sourcery-ai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors Playwright-based UI tests and related widgets to be more robust and PF6‑compatible, and standardizes local/CI test execution through Hatch-managed environments and scripts.

Sequence diagram for PF6-compatible radio.fill interaction

sequenceDiagram
    actor Test
    participant Radio
    participant Browser
    participant InputElement

    Test->>Radio: fill(True)
    Radio->>Radio: selected
    alt already selected or False
        Radio-->>Test: False
    else needs selection
        Radio->>Browser: element(RADIO_LOC)
        Browser-->>Radio: InputElement
        Radio->>InputElement: evaluate(js_checked_and_events)
        InputElement->>InputElement: dispatchEvent(click)
        InputElement->>InputElement: dispatchEvent(input)
        InputElement->>InputElement: dispatchEvent(change)
        Radio-->>Test: True
    end
Loading

File-Level Changes

Change Details Files
Move CI test workflow to Hatch-managed environments and Playwright installation scripts, and adjust PF version matrix naming.
  • Install Hatch instead of bare Playwright in CI and create the test environment before running tests.
  • Cache Hatch environments keyed by OS/Python version and use them to run Playwright browser installation via a Hatch script.
  • Change PF matrix values from v5/v6 to 5/6 and update job names and coverage combination.
  • Replace direct pytest invocations with hatch run test:pf5/pf6 including coverage and rerun options.
.github/workflows/tests.yaml
Add Playwright as a dev dependency and define Hatch test/lint environments, scripts, and pytest defaults.
  • Include Playwright in the dev extras for the project.
  • Define test Hatch environment with scripts for installing browsers, running PF5/PF6 tests, and a debug mode.
  • Define lint Hatch environment with a pre-commit-based lint script.
  • Configure pytest testpaths and global timeout under [tool.pytest.ini_options].
pyproject.toml
Make PF5 menu widget interactions more robust, especially for always-open menus and checkbox state handling.
  • Update Menu.item_element to special-case IS_ALWAYS_OPEN menus by locating items via text and returning the underlying input where available.
  • Wrap item_select and item_deselect in wait_for-based retry loops to ensure checkbox selection state is correctly toggled.
  • Simplify read to use the underlying input element’s is_checked state when present and default to False otherwise.
src/widgetastic_patternfly5/components/menus/menu.py
Improve dropdown behavior and PF5/PF6 compatibility, including open behavior, disabled state detection, and locators.
  • Use wait_for around the dropdown open click to ensure the menu is opened reliably.
  • Enhance item_enabled to account for PF6’s use of aria-disabled on child elements and add checks for pf-m-aria-disabled classes.
  • Extend TEXT_LOCATOR and DEFAULT_LOCATOR to support PF6 MenuToggle-based markup while remaining compatible with PF5.
  • Switch from the external cached_property package to functools.cached_property.
src/widgetastic_patternfly5/components/menus/dropdown.py
Adjust dropdown, alert, and navigation tests/widgets to work with PF6 markup and events.
  • Update dropdown test locator to find the first MenuToggle button with text "Dropdown" and use its parent as ROOT, avoiding PF5-specific IDs and OUIA attributes.
  • Scope alert tests to the "Alert variants" demo section to avoid interference from site-wide PF6 notification banners.
  • Change navigation selection to click the inner anchor/button via dispatch_event('click') and handle missing sub-items with an explicit NoSuchElementException.
testing/components/menus/test_dropdown_disabled.py
testing/components/test_alert.py
src/widgetastic_patternfly5/components/navigation.py
Update radio, select, chip, slider, and chart widgets for PF6-compatible, Playwright-friendly interactions.
  • Change radio selected to check the underlying input directly and implement fill using a JS checked setter plus click/input/change events.
  • In Select, wait for the create-item element to appear before clicking when create_item=True and value is new.
  • Use event dispatch for chip close instead of a direct click on the close button widget.
  • Improve slider fill by focusing the input, dispatching a change event, pressing Enter, and waiting until the displayed value matches.
  • Replace usages of the external cached_property with functools.cached_property in bullet chart.
src/widgetastic_patternfly5/components/forms/radio.py
src/widgetastic_patternfly5/components/menus/select.py
src/widgetastic_patternfly5/components/chip.py
src/widgetastic_patternfly5/components/slider.py
src/widgetastic_patternfly5/charts/bullet_chart.py
Make Playwright browser contexts more stable by setting a default timeout for all tests.
  • Set a 5-second default timeout on the Playwright browser context created in conftest.py.
testing/conftest.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • In ChipCategory.close (or the relevant chip category class), switching from self.close_button.click() to self.browser.element(CATEGORY_CLOSE) removes scoping to the widget root and may click the wrong close button when multiple chip categories are present; consider resolving CATEGORY_CLOSE relative to the component root instead of via the global browser.
  • In Select.fill, deriving a CSS selector by string-splitting CREATE_ITEM_LOCATOR to extract an @id assumes a very specific XPath shape and will break if the locator changes format; it would be more robust either to use the existing locator directly with Playwright or to store the bare ID/name separately rather than parsing it from the string.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `ChipCategory.close` (or the relevant chip category class), switching from `self.close_button.click()` to `self.browser.element(CATEGORY_CLOSE)` removes scoping to the widget root and may click the wrong close button when multiple chip categories are present; consider resolving `CATEGORY_CLOSE` relative to the component root instead of via the global browser.
- In `Select.fill`, deriving a CSS selector by string-splitting `CREATE_ITEM_LOCATOR` to extract an `@id` assumes a very specific XPath shape and will break if the locator changes format; it would be more robust either to use the existing locator directly with Playwright or to store the bare ID/name separately rather than parsing it from the string.

## Individual Comments

### Comment 1
<location path="src/widgetastic_patternfly5/components/forms/radio.py" line_range="52-61" />
<code_context>
     def fill(self, values):
         """Can only handle `True` to check the radio, nature of individual radio button"""
-        return self.radio.fill(values)
+        if values == self.selected:
+            return False
+        if values:
+            el = self.browser.element(self.RADIO_LOC)
+            el.evaluate(
+                "e => {"
+                "  const nativeSetter = Object.getOwnPropertyDescriptor("
+                "    window.HTMLInputElement.prototype, 'checked'"
+                "  ).set;"
+                "  nativeSetter.call(e, true);"
+                "  e.dispatchEvent(new Event('click', {bubbles: true}));"
+                "  e.dispatchEvent(new Event('input', {bubbles: true}));"
+                "  e.dispatchEvent(new Event('change', {bubbles: true}));"
+                "}"
+            )
+        return True
</code_context>
<issue_to_address>
**issue (bug_risk):** Clarify behavior when `values` is `False` for an already-checked radio.

With the current logic, calling `fill(False)` on a selected radio skips the early return, skips the `if values:` block, and still returns `True`, suggesting a change occurred when nothing actually changed.

Consider either treating `False` as a no-op that returns `False`, or explicitly disallowing `False` (e.g., by raising) and documenting that `fill` only supports `True`. Otherwise, callers may misinterpret the return value and assume the radio was updated when it was not.
</issue_to_address>

### Comment 2
<location path="src/widgetastic_patternfly5/components/menus/select.py" line_range="225-233" />
<code_context>
-            self.root_browser.click(self.CREATE_ITEM_LOCATOR)
+            from wait_for import wait_for as _wait_for
+
+            _id_attr = self.CREATE_ITEM_LOCATOR.split("@id='")[1].rstrip("']")
+            create_css = "#" + _id_attr
+            page = self.browser.element(".").page
+            _wait_for(
+                lambda: page.locator(create_css).count() > 0,
+                timeout=3,
+                delay=0.2,
+            )
+            page.locator(create_css).click()
             return True
         else:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid brittle string parsing of `CREATE_ITEM_LOCATOR` to derive the CSS selector.

This assumes `CREATE_ITEM_LOCATOR` is always an XPath with `@id='…'` in a specific format. Any change (quotes, extra predicates, non-XPath locator) will either raise `IndexError` or yield a wrong selector.

Instead, either:
- Use the locator directly via the browser abstraction (e.g. `self.root_browser.click(self.CREATE_ITEM_LOCATOR)` with waiting), or
- Locate the element with the existing locator and call `get_attribute('id')` to derive `#id`.

That avoids coupling this code to a specific string representation of the locator.
</issue_to_address>

### Comment 3
<location path="src/widgetastic_patternfly5/components/chip.py" line_range="249-250" />
<code_context>

     def close(self):
-        self.close_button.click()
+        close_el = self.browser.element(CATEGORY_CLOSE)
+        close_el.dispatch_event("click")

     @classmethod
</code_context>
<issue_to_address>
**issue (bug_risk):** Use the class attribute for `CATEGORY_CLOSE` and leverage the existing `close_button` widget.

`CATEGORY_CLOSE` is not defined in this scope and should be referenced as the class attribute (e.g. `self.CATEGORY_CLOSE`), otherwise this will raise a `NameError`.

Also, the previous version used `self.close_button`, which likely wraps the correct locator and behavior. To stay consistent with the existing API and avoid regressions, consider either:
- `close_el = self.browser.element(self.CATEGORY_CLOSE)`, or
- Preferably, `self.close_button.dispatch_event("click")` to reuse the widget abstraction.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/widgetastic_patternfly5/components/forms/radio.py Outdated
Comment thread src/widgetastic_patternfly5/components/menus/select.py Outdated
Comment thread src/widgetastic_patternfly5/components/chip.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This pull request updates the library’s PatternFly widget interactions and the accompanying Playwright-based test suite to be more reliable across PatternFly v5 and v6, while also standardizing local/CI test execution through Hatch environments.

Changes:

  • Improve widget interaction robustness (dropdown/menu selection, radio selection, slider input updates, chip-group close behavior) to better align with PF6 DOM/event behavior.
  • Stabilize UI tests by scoping locators to PF demo sections and using PF5/PF6-safe locators.
  • Add Hatch-managed test/lint environments and refactor GitHub Actions CI to run the PF5/PF6 matrix via Hatch scripts.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
testing/conftest.py Sets Playwright context default timeout to improve test reliability.
testing/components/test_alert.py Scopes alert selection to the demo section to avoid site-wide PF6 alerts.
testing/components/menus/test_dropdown_disabled.py Updates dropdown root locator to work across PF5/PF6 markup differences.
src/widgetastic_patternfly5/components/slider.py Adjusts InputSlider filling to be more event-accurate and adds a post-fill wait.
src/widgetastic_patternfly5/components/navigation.py Changes navigation expansion to click the actionable element via dispatched click.
src/widgetastic_patternfly5/components/menus/select.py Makes typeahead “create item” selection more Playwright-friendly with explicit waiting.
src/widgetastic_patternfly5/components/menus/menu.py Improves always-open menu item lookup and adds retry loops for checkbox select/deselect.
src/widgetastic_patternfly5/components/menus/dropdown.py Adds retry/wait logic to dropdown open to reduce flakiness.
src/widgetastic_patternfly5/components/forms/radio.py Reworks radio selection to operate on the underlying input and dispatch DOM events.
src/widgetastic_patternfly5/components/chip.py Changes category chip-group close to use an event dispatch approach.
pyproject.toml Adds Playwright dev dependency; introduces Hatch envs/scripts; configures pytest defaults.
.github/workflows/tests.yaml Migrates CI to Hatch-managed envs and scripts; updates PF version matrix handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/widgetastic_patternfly5/components/chip.py Outdated
Comment thread src/widgetastic_patternfly5/components/forms/radio.py Outdated
Comment thread src/widgetastic_patternfly5/components/slider.py
Comment thread src/widgetastic_patternfly5/components/menus/select.py Outdated
@mshriver
mshriver force-pushed the pf6-test-fixes-hatch branch from 9f0fd29 to 84fc0eb Compare June 3, 2026 09:27
@codecov

codecov Bot commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.23944% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.64%. Comparing base (622b010) to head (5d6e58c).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...c/widgetastic_patternfly5/components/menus/menu.py 65.51% 10 Missing ⚠️
...c/widgetastic_patternfly5/components/navigation.py 14.28% 6 Missing ⚠️
.../widgetastic_patternfly5/components/forms/radio.py 66.66% 2 Missing ⚠️
src/widgetastic_patternfly5/components/slider.py 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #126      +/-   ##
==========================================
- Coverage   91.23%   90.64%   -0.60%     
==========================================
  Files          38       38              
  Lines        2191     2244      +53     
==========================================
+ Hits         1999     2034      +35     
- Misses        192      210      +18     
Flag Coverage Δ
unittests 90.64% <73.23%> (-0.60%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mshriver
mshriver force-pushed the pf6-test-fixes-hatch branch from 84fc0eb to 540f821 Compare June 3, 2026 14:05
@mshriver
mshriver requested a review from Copilot June 4, 2026 06:02
@mshriver mshriver added the bug Something isn't working label Jun 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

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

Comment thread src/widgetastic_patternfly5/components/forms/radio.py Outdated
Comment thread src/widgetastic_patternfly5/components/navigation.py
Comment thread src/widgetastic_patternfly5/components/menus/select.py Outdated
@mshriver

Copy link
Copy Markdown
Collaborator Author

@sourcery-ai review

@mshriver

Copy link
Copy Markdown
Collaborator Author

@copilot review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In Chip.close, calling self.browser.element(self.close_button) is likely incorrect since close_button is a widget rather than a locator; consider dispatching the click on self.close_button's underlying element/locator instead (similar to the previous implementation).
  • Several new wait_for calls use hard-coded timeouts and delays (e.g., 10s/0.5s); consider centralizing these values or tuning them per widget to avoid unnecessarily lengthening test runs while still preserving robustness.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `Chip.close`, calling `self.browser.element(self.close_button)` is likely incorrect since `close_button` is a widget rather than a locator; consider dispatching the click on `self.close_button`'s underlying element/locator instead (similar to the previous implementation).
- Several new `wait_for` calls use hard-coded timeouts and delays (e.g., 10s/0.5s); consider centralizing these values or tuning them per widget to avoid unnecessarily lengthening test runs while still preserving robustness.

## Individual Comments

### Comment 1
<location path="src/widgetastic_patternfly5/components/chip.py" line_range="251" />
<code_context>

     def close(self):
-        self.close_button.click()
+        self.browser.element(self.close_button).dispatch_event("click")

     @classmethod
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `self.close_button` as the locator for `browser.element` is likely incorrect and may break closing behavior.

Previously `self.close_button.click()` invoked the click directly on the widget. Now `self.close_button` is passed to `browser.element`, which usually expects a locator/selector, not a widget, so this may fail at runtime or target the wrong element. Please either trigger the click on `self.close_button` itself (e.g. `self.close_button.dispatch_event("click")`, if available) or pass an appropriate locator for the close button into `browser.element` before dispatching the event.
</issue_to_address>

### Comment 2
<location path="src/widgetastic_patternfly5/components/forms/radio.py" line_range="57-66" />
<code_context>
+        Returns:
+            ``True`` if the state changed, ``False`` otherwise.
+        """
+        if not values or self.selected:
+            return False
+        el = self.browser.element(self.RADIO_LOC)
+        el.evaluate(
+            "e => {"
+            "  const nativeSetter = Object.getOwnPropertyDescriptor("
+            "    window.HTMLInputElement.prototype, 'checked'"
+            "  ).set;"
+            "  nativeSetter.call(e, true);"
+            "  e.dispatchEvent(new Event('click', {bubbles: true}));"
+            "  e.dispatchEvent(new Event('input', {bubbles: true}));"
+            "  e.dispatchEvent(new Event('change', {bubbles: true}));"
+            "}"
+        )
+        return True
</code_context>
<issue_to_address>
**issue (bug_risk):** The new `fill` implementation does not take `disabled` radios into account and may force-change their state.

Previously, `fill` delegated to `self.radio.fill`, which likely respected disabled state. This implementation unconditionally sets `checked = true` via JS whenever `values` is truthy and the radio isn’t already selected, ignoring `self.disabled`. That can change the state of a disabled radio, which is incorrect and may hide UI problems. Please add a guard for `self.disabled` (e.g., return `False` or raise) before mutating the element.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/widgetastic_patternfly5/components/chip.py Outdated
Comment thread src/widgetastic_patternfly5/components/forms/radio.py Outdated
@mshriver
mshriver force-pushed the pf6-test-fixes-hatch branch from 7d79082 to 08de899 Compare July 21, 2026 15:05
mshriver and others added 3 commits July 21, 2026 11:50
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
@mshriver
mshriver force-pushed the pf6-test-fixes-hatch branch from 08de899 to 140b6f2 Compare July 21, 2026 16:11
mshriver and others added 2 commits July 21, 2026 12:46
In Playwright, set_default_timeout() applies to ALL operations including
navigation. The 5s default is too aggressive for page.goto() calls with
wait_until='networkidle' against external patternfly.org sites in CI
environments where network latency is higher.

Use set_default_navigation_timeout(30_000) explicitly so navigation gets
a 30s timeout while element interactions keep the 5s timeout.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
@mshriver
mshriver force-pushed the pf6-test-fixes-hatch branch from ad2a541 to 5d6e58c Compare July 21, 2026 17:27
@mshriver
mshriver merged commit efc37f8 into RedHatQE:main Jul 21, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants