widgetastic.patternfly5 25.7.30.0__py3-none-any.whl → 25.12.15.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,10 +11,10 @@ class AlertsTimelineChart(LineChart):
11
11
  locator: If you have specific locator else it will take pf-chart.
12
12
  """
13
13
 
14
- Y_AXIS_ROW = "./*[name()='svg']/*[name()='g'][3]/*[name()='g']"
15
- Y_AXIS_ROW_LINE = "./*[name()='path']"
14
+ Y_AXIS_ROW = ".//*[name()='svg']/*[name()='g'][3]/*[name()='g']"
15
+ Y_AXIS_ROW_LINE = ".//*[name()='path']"
16
16
 
17
- TOOLTIP = "./*[name()='svg']/*[name()='g'][5]"
17
+ TOOLTIP = ".//*[name()='svg']/*[name()='g'][5]"
18
18
  TOOLTIP_X_AXIS_LABLE = None
19
19
  TOOLTIP_LABLES = None
20
20
  TOOLTIP_VALUES = ".//*[name()='text']/*[name()='tspan']"
@@ -48,7 +48,8 @@ class AlertsTimelineChart(LineChart):
48
48
  _row_data = []
49
49
  for line_el in lines_el:
50
50
  self.browser.move_to_element(line_el)
51
- self.browser.click(line_el)
51
+ # Sometime path elements are not interactable so use force click.
52
+ self.browser.click(line_el, force=True)
52
53
  tooltip_el = self.browser.wait_for_element(self.TOOLTIP)
53
54
 
54
55
  label_data = {}
@@ -1,5 +1,7 @@
1
1
  import re
2
+ import time
2
3
 
4
+ from cached_property import cached_property
3
5
  from widgetastic.utils import ParametrizedLocator
4
6
  from widgetastic.widget import Text, View
5
7
  from widgetastic.xpath import quote
@@ -21,7 +23,7 @@ class BulletChart(View):
21
23
  ROOT = ParametrizedLocator("{@locator}")
22
24
 
23
25
  DEFAULT_LOCATOR = ".//div[contains(@class, 'chartbullet')]"
24
- ITEMS = ".//*[name()='g']/*[name()='path' and not(contains(@style, 'type:square'))]"
26
+ ITEMS = "//*[name()='g' and not(./*[name()='rect'])]/*[name()='path']"
25
27
  TOOLTIP_REGEX = re.compile(r"(.*?): ([\d]+)")
26
28
  APPLY_OFFSET = True
27
29
 
@@ -73,20 +75,20 @@ class BulletChart(View):
73
75
  except StopIteration:
74
76
  return None
75
77
 
76
- @property
78
+ @cached_property
77
79
  def data(self):
78
80
  """Read graph and returns all Data Point objects."""
79
81
  _data = []
80
82
  # focus away from graph
81
83
  self.parent_browser.move_to_element("//body")
82
-
83
84
  for el in self.browser.elements(self.ITEMS):
84
- self.browser.move_to_element(el)
85
- self.browser.click(el)
85
+ time.sleep(0.2)
86
+ # Sometime path elements are not interactable so use force click.
87
+ self.browser.click(el, force=True)
86
88
 
87
89
  if self.APPLY_OFFSET:
88
90
  dx, dy = self._offsets(el)
89
- self.browser.move_by_offset(dx, dy)
91
+ self.browser.move_by_offset(origin=el, x=dx, y=dy)
90
92
 
91
93
  match = self.TOOLTIP_REGEX.match(self.tooltip.text)
92
94
  if match:
@@ -94,7 +96,7 @@ class BulletChart(View):
94
96
  DataPoint(
95
97
  label=match.groups()[0],
96
98
  value=int(match.groups()[1]),
97
- color=el.value_of_css_property("fill"),
99
+ color=self.browser.value_of_css_property(el, "fill"),
98
100
  )
99
101
  )
100
102
  return _data
@@ -5,9 +5,9 @@ from widgetastic.xpath import quote
5
5
 
6
6
 
7
7
  class DonutLegendItem(ParametrizedView, ClickableMixin):
8
- PARAMETERS = ("label_text",)
8
+ PARAMETERS = ("label_name",)
9
9
  ROOT = ParametrizedLocator(
10
- ".//*[name()='text']/*[name()='tspan' and contains(., '{label_text}')]"
10
+ ".//*[name()='text']/*[name()='tspan' and contains(., '{label_name}:')]"
11
11
  )
12
12
  ALL_ITEMS = ".//*[name()='text']/*[name()='tspan']"
13
13
  LEGEND_ITEM_REGEX = re.compile(r"(.*?): ([\d]+)")
@@ -32,8 +32,14 @@ class DonutLegendItem(ParametrizedView, ClickableMixin):
32
32
 
33
33
  @classmethod
34
34
  def all(cls, browser):
35
- """Returns a list of all items"""
36
- return [(browser.text(el),) for el in browser.elements(cls.ALL_ITEMS)]
35
+ """Returns a list of all item labels"""
36
+ result = []
37
+ for el in browser.elements(cls.ALL_ITEMS):
38
+ text = browser.text(el)
39
+ # Extract just the label
40
+ label, _ = cls._get_legend_item(text)
41
+ result.append((label,))
42
+ return result
37
43
 
38
44
 
39
45
  class DonutLegend(View):
@@ -32,9 +32,9 @@ class Legend(ParametrizedView):
32
32
  self.browser.elements(self.LEGEND_ICON_ITEMS),
33
33
  self.browser.elements(self.LEGEND_LABEL_ITEMS),
34
34
  ):
35
- color = icon.value_of_css_property("fill")
36
- if not color:
37
- color = icon.value_of_css_property("color")
35
+ color = self.browser.value_of_css_property(
36
+ icon, "fill"
37
+ ) or self.browser.value_of_css_property(icon, "color")
38
38
  _data[self.browser.text(label_el)] = color
39
39
  return _data
40
40
 
@@ -83,9 +83,9 @@ class LineChart(View):
83
83
  _data = {}
84
84
 
85
85
  for lab_el in self._x_axis_labels_map.values():
86
- self.browser.move_to_element(lab_el)
87
- self.browser.click(lab_el)
88
- self.browser.move_by_offset(*offset)
86
+ # Sometime path elements are not interactable so use force click.
87
+ self.browser.click(lab_el, force=True)
88
+ self.browser.move_by_offset(lab_el, *offset)
89
89
  tooltip_el = self.browser.wait_for_element(self.TOOLTIP)
90
90
 
91
91
  x_axis_label = self.browser.text(self.TOOLTIP_X_AXIS_LABLE, parent=tooltip_el)
@@ -20,10 +20,7 @@ class BaseClipboardCopy:
20
20
  def is_editable(self):
21
21
  if self.is_inline:
22
22
  return False
23
- if self.browser.get_attribute("readonly", self.text):
24
- return False
25
- else:
26
- return True
23
+ return "readonly" not in self.browser.attributes(self.text)
27
24
 
28
25
  @property
29
26
  def is_inline(self):
@@ -1,5 +1,4 @@
1
- from selenium.common.exceptions import NoSuchElementException
2
- from selenium.webdriver.common.keys import Keys
1
+ from widgetastic.exceptions import NoSuchElementException
3
2
  from widgetastic.utils import ParametrizedLocator
4
3
  from widgetastic.widget import Widget
5
4
  from widgetastic.xpath import quote
@@ -17,18 +16,15 @@ class BaseCalendarMonth:
17
16
  https://www.patternfly.org/components/date-and-time/calendar-month
18
17
  """
19
18
 
20
- CALENDAR_HEADER = ".//div[contains(@class, '-c-calendar-month__header')]"
21
- MONTH_SELECT_LOCATOR = f"{CALENDAR_HEADER}//div[contains(@class, 'header-month')]"
19
+ MONTH_SELECT_LOCATOR = ".//div[contains(@class, '-c-calendar-month__header-month')]"
22
20
  _month_select_widget = Select(locator=MONTH_SELECT_LOCATOR)
23
- YEAR_INPUT_LOCATOR = (
24
- f"{CALENDAR_HEADER}//div[contains(@class, '-c-calendar-month__header-year')]//input"
25
- )
21
+ YEAR_INPUT_LOCATOR = ".//div[contains(@class, '-c-calendar-month__header-year')]//input"
26
22
  DATE_LOCATOR = (
27
23
  ".//button[text()={date} and not(ancestor::td[contains(@class, 'pf-m-adjacent-month')])]"
28
24
  )
29
25
 
30
- PREV_BUTTON_LOCATOR = f"{CALENDAR_HEADER}//div[contains(@class, 'prev-month')]"
31
- NEXT_BUTTON_LOCATOR = f"{CALENDAR_HEADER}//div[contains(@class, 'next-month')]"
26
+ PREV_BUTTON_LOCATOR = ".//div[contains(@class, 'prev-month')]"
27
+ NEXT_BUTTON_LOCATOR = ".//div[contains(@class, 'next-month')]"
32
28
 
33
29
  TABLE = ".//table"
34
30
  SELECTED_DATE_LOCATOR = f"{TABLE}/tbody//td[contains(@class, 'pf-m-selected')]"
@@ -46,14 +42,14 @@ class BaseCalendarMonth:
46
42
 
47
43
  @year.setter
48
44
  def year(self, value):
45
+ self.browser.fill(str(value), self.YEAR_INPUT_LOCATOR)
46
+ # value attribute not setting at same time we need release that web element.
49
47
  el = self.browser.element(self.YEAR_INPUT_LOCATOR)
50
- el.send_keys(Keys.CONTROL + "a")
51
- el.send_keys(str(value) + Keys.ENTER)
48
+ self.browser.execute_script("arguments[0].blur();", el)
52
49
 
53
50
  @property
54
51
  def month(self):
55
- el = self.browser.element(self.MONTH_SELECT_LOCATOR)
56
- return self.browser.text(el)
52
+ return self._month_select_widget.read()
57
53
 
58
54
  @month.setter
59
55
  def month(self, value):
@@ -62,10 +58,9 @@ class BaseCalendarMonth:
62
58
  @property
63
59
  def day(self):
64
60
  try:
65
- el = self.browser.element(self.SELECTED_DATE_LOCATOR)
61
+ return self.browser.text(self.SELECTED_DATE_LOCATOR)
66
62
  except NoSuchElementException:
67
63
  return ""
68
- return self.browser.text(el)
69
64
 
70
65
  @day.setter
71
66
  def day(self, value):
@@ -25,11 +25,11 @@ class BaseDualListSelector:
25
25
 
26
26
  @property
27
27
  def _available(self):
28
- return self.browser.element(self.AVAILABLE, parent=self)
28
+ return self.browser.element(self.AVAILABLE)
29
29
 
30
30
  @property
31
31
  def _chosen(self):
32
- return self.browser.element(self.CHOSEN, parent=self)
32
+ return self.browser.element(self.CHOSEN)
33
33
 
34
34
  @property
35
35
  def _left_list(self):
@@ -41,11 +41,11 @@ class BaseDualListSelector:
41
41
 
42
42
  @property
43
43
  def _left_title(self):
44
- return self.browser.element(self.SECTION_TITLE, parent=self._available).text
44
+ return self.browser.text(self.SECTION_TITLE, parent=self._available)
45
45
 
46
46
  @property
47
47
  def _right_title(self):
48
- return self.browser.element(self.SECTION_TITLE, parent=self._chosen).text
48
+ return self.browser.text(self.SECTION_TITLE, parent=self._chosen)
49
49
 
50
50
  @property
51
51
  def _left_elements(self):
@@ -86,8 +86,8 @@ class BaseDualListSelector:
86
86
  right_elements = self._right_elements
87
87
  left_elements = self._left_elements
88
88
 
89
- data[self._left_title] = [el.text for el in left_elements]
90
- data[self._right_title] = [el.text for el in right_elements]
89
+ data[self._left_title] = [self.browser.text(el) for el in left_elements]
90
+ data[self._right_title] = [self.browser.text(el) for el in right_elements]
91
91
  return data
92
92
 
93
93
  def reset_selected(self, left_items=True):
@@ -1,4 +1,3 @@
1
- from selenium.webdriver.support.ui import Select
2
1
  from widgetastic.exceptions import NoSuchElementException
3
2
  from widgetastic.widget import GenericLocatorWidget
4
3
 
@@ -68,10 +67,6 @@ class BaseFormSelect:
68
67
  result.append(self.browser.text(el))
69
68
  return result
70
69
 
71
- @property
72
- def _select_element(self):
73
- return Select(self.__element__())
74
-
75
70
  def fill(self, value):
76
71
  """Select desired option in FormSelect.
77
72
 
@@ -90,11 +85,11 @@ class BaseFormSelect:
90
85
  raise FormSelectOptionDisabled(
91
86
  f'Option "{value}" is disabled in {repr(self)}. Enabled options are: {self.all_enabled_options}'
92
87
  )
93
- self._select_element.select_by_visible_text(value)
88
+ self.__element__().select_option(label=value)
94
89
 
95
90
  def read(self):
96
91
  """Returns selected option."""
97
- return self.browser.text(self._select_element.first_selected_option)
92
+ return self.browser.text("option:checked")
98
93
 
99
94
  def __repr__(self):
100
95
  return f"{type(self).__name__}({self.locator!r})"
@@ -1,7 +1,7 @@
1
1
  from contextlib import contextmanager
2
2
 
3
3
  from cached_property import cached_property
4
- from widgetastic.exceptions import NoSuchElementException, UnexpectedAlertPresentException
4
+ from widgetastic.exceptions import NoSuchElementException
5
5
  from widgetastic.utils import ParametrizedLocator
6
6
  from widgetastic.widget import Widget
7
7
  from widgetastic.xpath import quote
@@ -87,7 +87,7 @@ class BaseDropdown:
87
87
  # def _click():
88
88
  # self.browser.click(self.BUTTON_LOCATOR)
89
89
  # return self.is_open
90
- el = self.browser.wait_for_element(self.BUTTON_LOCATOR)
90
+ el = self.browser.element(self.BUTTON_LOCATOR)
91
91
  self.browser.click(el)
92
92
  return self.is_open
93
93
 
@@ -202,11 +202,7 @@ class BaseDropdown:
202
202
  self.browser.handle_alert(cancel=not handle_alert, wait=10.0)
203
203
  self.browser.plugin.ensure_page_safe()
204
204
  finally:
205
- try:
206
- self.close(ignore_nonpresent=True)
207
- except UnexpectedAlertPresentException:
208
- self.logger.warning("There is an unexpected alert present.")
209
- pass
205
+ self.close(ignore_nonpresent=True)
210
206
 
211
207
  @property
212
208
  def button_text(self):
@@ -208,7 +208,7 @@ class BaseCheckboxMenu(BaseMenu):
208
208
  # get the child element of the label
209
209
  selected[item] = self.browser.element(
210
210
  parent=el, locator=".//input"
211
- ).is_selected()
211
+ ).is_checked()
212
212
  except NoSuchElementException:
213
213
  selected[item] = False
214
214
 
@@ -157,7 +157,7 @@ class BaseCheckboxSelect(BaseSelect):
157
157
  # get the child element of the label
158
158
  selected[item] = self.browser.element(
159
159
  parent=el, locator=".//input"
160
- ).is_selected()
160
+ ).is_checked()
161
161
  except NoSuchElementException:
162
162
  selected[item] = False
163
163
 
@@ -56,9 +56,7 @@ class BaseNavigation:
56
56
  def nav_links(self, *levels):
57
57
  """Returns a list of all navigation items."""
58
58
  if not levels:
59
- return [
60
- el.get_property("textContent").strip() for el in self.browser.elements(self.ITEMS)
61
- ]
59
+ return [self.browser.text(el) for el in self.browser.elements(self.ITEMS)]
62
60
  current_item = self
63
61
  for i, level in enumerate(levels):
64
62
  li = self.browser.element(
@@ -73,8 +71,7 @@ class BaseNavigation:
73
71
  else:
74
72
  raise
75
73
  return [
76
- el.get_property("textContent").strip()
77
- for el in self.browser.elements(self.ITEMS, parent=current_item)
74
+ self.browser.text(el) for el in self.browser.elements(self.ITEMS, parent=current_item)
78
75
  ]
79
76
 
80
77
  @check_nav_loaded
@@ -1,7 +1,6 @@
1
1
  import math
2
2
  from contextlib import contextmanager
3
3
 
4
- from selenium.webdriver.common.keys import Keys
5
4
  from widgetastic.utils import ParametrizedLocator
6
5
  from widgetastic.widget import GenericLocatorWidget, Text, TextInput, View
7
6
 
@@ -43,7 +42,7 @@ class BasePagination:
43
42
  Returns ``True`` when pagination dropdown button is enabled along with next & last button.
44
43
  """
45
44
  el = self.browser.element(self._last)
46
- last_flag = el.is_enabled() if el.is_displayed() else True
45
+ last_flag = el.is_enabled() if el.is_visible() else True
47
46
  return (
48
47
  self.browser.element(self._options.BUTTON_LOCATOR).is_enabled()
49
48
  and self.browser.element(self._next).is_enabled()
@@ -178,7 +177,7 @@ class BasePagination:
178
177
  def go_to_page(self, value):
179
178
  """Navigate to custom page number."""
180
179
  self._current_page.fill(value)
181
- self.browser.send_keys(Keys.RETURN, self._current_page)
180
+ self._current_page.__element__().press("Enter")
182
181
 
183
182
  def __iter__(self):
184
183
  if self.current_page > 1:
@@ -1,4 +1,3 @@
1
- from selenium.webdriver.common.keys import Keys
2
1
  from widgetastic.widget import GenericLocatorWidget
3
2
 
4
3
 
@@ -12,7 +11,7 @@ class BaseSlider:
12
11
  THUMB = ".//div[contains(@class, '-c-slider__thumb')]"
13
12
  STEPS = (
14
13
  ".//div[contains(@class, '-c-slider__steps')]"
15
- "/child::div[contains(@class, '-c-slider__step')]"
14
+ "//child::div[contains(@class, '-c-slider__step-tick')]"
16
15
  )
17
16
 
18
17
  def _str_num(self, value):
@@ -79,7 +78,7 @@ class BaseSlider:
79
78
  source_el = el_map[self.text] if self.text in el_map else self.browser.element(self.THUMB)
80
79
  self.browser.move_to_element(source_el)
81
80
  self.browser.drag_and_drop(source_el, target_el)
82
- self.browser.click(target_el)
81
+ self.browser.click(target_el, force=True)
83
82
  return True
84
83
 
85
84
  def read(self):
@@ -99,6 +98,7 @@ class InputSlider(Slider):
99
98
  if self.text == value:
100
99
  return False
101
100
  el = self.browser.element(self.INPUT)
102
- el.send_keys(Keys.CONTROL + "a")
103
- el.send_keys(str(value) + Keys.ENTER)
101
+ el.press("Control+A")
102
+ el.fill(str(value))
103
+ el.press("Enter")
104
104
  return True
@@ -12,10 +12,10 @@ class BaseSwitch:
12
12
  https://www.patternfly.org/components/switch
13
13
  """
14
14
 
15
- CHECKBOX_LOCATOR = "./input"
16
- PF_5_LABEL_ON = "./span[contains(@class, 'pf-m-on')]"
17
- PF_5_LABEL_OFF = "./span[contains(@class, 'pf-m-off')]"
18
- PF_6_LABLE = "./span[contains(@class, '-c-switch__label')]"
15
+ CHECKBOX_LOCATOR = ".//input"
16
+ PF_5_LABEL_ON = ".//span[contains(@class, 'pf-m-on')]"
17
+ PF_5_LABEL_OFF = ".//span[contains(@class, 'pf-m-off')]"
18
+ PF_6_LABEL = ".//span[contains(@class, '-c-switch__label')]"
19
19
 
20
20
  @property
21
21
  def is_enabled(self):
@@ -26,32 +26,36 @@ class BaseSwitch:
26
26
  """Click on a Switch."""
27
27
  if not self.is_enabled:
28
28
  raise SwitchDisabled(f"{repr(self)} is disabled")
29
- else:
30
- self.browser.click(self.CHECKBOX_LOCATOR)
31
- return True
29
+
30
+ self.browser.click(self.CHECKBOX_LOCATOR, force=True)
31
+ return True
32
32
 
33
33
  @property
34
34
  def selected(self):
35
35
  """Returns a boolean detailing if the Switch is on (True) of off (False)."""
36
- return self.browser.get_attribute("checked", self.CHECKBOX_LOCATOR) is not None
36
+ return self.browser.is_checked(self.CHECKBOX_LOCATOR)
37
37
 
38
- def fill(self, value):
38
+ def fill(self, value: bool):
39
39
  """Fills a Switch with the supplied value."""
40
40
  if not self.is_enabled:
41
41
  raise SwitchDisabled(f"{repr(self)} is disabled")
42
- if bool(value) == self.selected:
42
+ if value == self.selected:
43
43
  return False
44
+
45
+ el = self.browser.element(self.CHECKBOX_LOCATOR)
46
+ if value:
47
+ el.check(force=True)
44
48
  else:
45
- self.browser.click(self.CHECKBOX_LOCATOR)
46
- return True
49
+ el.uncheck(force=True)
50
+ return True
47
51
 
48
52
  @property
49
53
  def label(self):
50
54
  """Returns the label of the Switch."""
51
55
  if self.selected:
52
- return self._read_locator(self.PF_5_LABEL_ON) or self._read_locator(self.PF_6_LABLE)
56
+ return self._read_locator(self.PF_5_LABEL_ON) or self._read_locator(self.PF_6_LABEL)
53
57
  else:
54
- return self._read_locator(self.PF_5_LABEL_OFF) or self._read_locator(self.PF_6_LABLE)
58
+ return self._read_locator(self.PF_5_LABEL_OFF) or self._read_locator(self.PF_6_LABEL)
55
59
 
56
60
  def read(self):
57
61
  """Returns a boolean detailing if the Switch is on (True) of off (False)."""
@@ -1,4 +1,4 @@
1
- from selenium.common.exceptions import NoSuchElementException
1
+ from widgetastic.exceptions import NoSuchElementException
2
2
  from widgetastic.log import create_item_logger
3
3
  from widgetastic.widget import Table, TableColumn, TableRow, Text, Widget
4
4
  from widgetastic.widget.table import resolve_table_widget
@@ -302,7 +302,7 @@ class ExpandableTable(BaseExpandableTable, PatternflyTable):
302
302
 
303
303
 
304
304
  class ExpandableColumn(TableColumn):
305
- EXPAND_LOCATOR = "./button"
305
+ EXPAND_LOCATOR = ".//button"
306
306
 
307
307
  def __init__(
308
308
  self,
@@ -9,11 +9,11 @@ class BaseTitle:
9
9
 
10
10
  @property
11
11
  def heading_level(self):
12
- return self.browser.element(self, parent=self.parent).tag_name
12
+ return self.browser.tag(self)
13
13
 
14
14
  @property
15
15
  def text(self):
16
- return self.browser.text(self, parent=self.parent)
16
+ return self.browser.text(self)
17
17
 
18
18
  def read(self):
19
19
  return self.text
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: widgetastic.patternfly5
3
- Version: 25.7.30.0
3
+ Version: 25.12.15.0a1
4
4
  Summary: Patternfly5 widget library for Widgetastic.
5
5
  Project-URL: repository, https://github.com/RedHatQE/widgetastic.patternfly5
6
6
  Maintainer-email: Nikhil Dhandre <ndhandre@redhat.com>, Egor Shamardin <eshamard@redhat.com>, Mike Shriver <mshriver@redhat.com>
@@ -28,13 +28,14 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
28
  Classifier: Topic :: Software Development :: Quality Assurance
29
29
  Classifier: Topic :: Software Development :: Testing
30
30
  Requires-Python: >=3.11
31
- Requires-Dist: widgetastic-core>=1.0.6
31
+ Requires-Dist: widgetastic-core>=2.0.0a2
32
32
  Provides-Extra: dev
33
33
  Requires-Dist: codecov; extra == 'dev'
34
34
  Requires-Dist: pre-commit; extra == 'dev'
35
35
  Requires-Dist: pytest; extra == 'dev'
36
36
  Requires-Dist: pytest-cov; extra == 'dev'
37
37
  Requires-Dist: pytest-rerunfailures; extra == 'dev'
38
+ Requires-Dist: pytest-timeout; extra == 'dev'
38
39
  Requires-Dist: pytest-xdist; extra == 'dev'
39
40
  Provides-Extra: doc
40
41
  Requires-Dist: sphinx; extra == 'doc'
@@ -60,11 +61,31 @@ Description-Content-Type: text/markdown
60
61
  </a>
61
62
  </p>
62
63
 
64
+ ## Overview
65
+
63
66
  This library offers Widgetastic Widgets for [PatternFly v5/v6](https://www.patternfly.org/), serving as an extended
64
- itteration of [widgetastic.patternfly4](https://github.com/RedHatQE/widgetastic.patternfly4).
67
+ iteration of [widgetastic.patternfly4](https://github.com/RedHatQE/widgetastic.patternfly4).
68
+
69
+ Built on top of [widgetastic.core](https://github.com/RedHatQE/widgetastic.core) with **Playwright** as the browser
70
+ automation engine, this library provides a robust and modern approach to UI testing for PatternFly components.
71
+
72
+ > **Note**: The `main` branch now uses **Playwright** for browser automation. For the legacy Selenium implementation, please refer to the [legacy-selenium-support](https://github.com/RedHatQE/widgetastic.patternfly5/tree/legacy-selenium-support) branch.
73
+
74
+ ## Installation
75
+
76
+ ```bash
77
+ # Install from PyPI
78
+ pip install widgetastic.patternfly5
79
+
80
+ # Or install from source
81
+ git clone https://github.com/RedHatQE/widgetastic.patternfly5.git
82
+ cd widgetastic.patternfly5
83
+ pip install -e .
84
+ ```
65
85
 
86
+ ## Supported Components
66
87
 
67
- ### Components:
88
+ ### Components
68
89
  - [alert](https://www.patternfly.org/components/alert)
69
90
  - [breadcrumb](https://www.patternfly.org/components/breadcrumb)
70
91
  - [button](https://www.patternfly.org/components/button)
@@ -107,9 +128,10 @@ itteration of [widgetastic.patternfly4](https://github.com/RedHatQE/widgetastic.
107
128
  - [line-chart](https://www.patternfly.org/charts/line-chart)
108
129
  - [pie-chart](https://www.patternfly.org/charts/pie-chart)
109
130
 
110
- ### Patterns:
131
+ ### Patterns
111
132
  - [card-view](https://www.patternfly.org/patterns/card-view)
112
133
 
134
+ ## Development
113
135
 
114
136
  ### Contribution guide
115
137
 
@@ -130,24 +152,91 @@ pip install -e .[dev]
130
152
  # if you use zsh, pip install will fail. Use this instead:
131
153
  pip install -e ".[dev]"
132
154
 
155
+ # install Playwright browsers for testing
156
+ playwright install chromium firefox
157
+ playwright install-deps
158
+
159
+ # setup pre-commit hooks
133
160
  pre-commit install
134
161
  ```
135
162
 
136
163
  ### Testing
137
164
 
138
- The library has selenium tests that are performed against [Patternfly v6 docs](https://www.patternfly.org) and [Patternfly v5 docs](https://v5-archive.patternfly.org).
139
- It's also configured to run the tests every time when a new version of that page is released.
165
+ The library includes comprehensive tests that run against the official PatternFly documentation pages:
166
+ - [PatternFly v6](https://www.patternfly.org) (latest)
167
+ - [PatternFly v5](https://v5-archive.patternfly.org) (archived)
168
+
169
+ Tests are powered by **Playwright**, providing fast, reliable, and modern browser automation.
170
+
171
+ #### Prerequisites
172
+
173
+ Before running tests, install Playwright browsers:
174
+
175
+ ```bash
176
+ # Install Playwright (included in dev dependencies)
177
+ pip install -e ".[dev]"
178
+
179
+ # Install Playwright browsers
180
+ playwright install chromium firefox
181
+
182
+ # Install system dependencies (if needed)
183
+ playwright install-deps
184
+ ```
185
+
186
+ #### Running Tests
187
+
188
+ **Basic test execution:**
189
+
190
+ ```bash
191
+ # Run tests with default settings (chromium, v6, headed mode)
192
+ pytest -v
193
+
194
+ # Run tests against PatternFly v5
195
+ pytest -v --pf-version v5
196
+
197
+ # Run tests with Firefox
198
+ pytest -v --browser firefox
199
+
200
+ # Run tests in headless mode (no browser window)
201
+ pytest -v --headless
202
+ ```
203
+
204
+ **Advanced options:**
205
+
206
+ ```bash
207
+ # Run tests in parallel (speeds up execution)
208
+ pytest -v -n 3 --browser chromium --pf-version v6
209
+
210
+ # Run with slow motion for debugging (100ms delay between actions)
211
+ pytest -v --slowmo 100
212
+
213
+ # Run specific test file
214
+ pytest testing/components/test_button.py -v --browser firefox
215
+
216
+ # Run tests with coverage
217
+ pytest -v --cov=./ --cov-report=html
218
+ ```
219
+
220
+ **Available test options:**
140
221
 
141
- Tests spawn a container from official selenium image - [selenium/standalone-{chrome/firefox}](https://hub.docker.com/u/selenium).
142
- We can check local runs via vnc `http://localhost:7900`
222
+ | Option | Choices | Default | Description |
223
+ |--------|---------|---------|-------------|
224
+ | `--browser` | `chromium`, `firefox` | `chromium` | Browser to use for testing |
225
+ | `--pf-version` | `v5`, `v6` | `v6` | PatternFly version to test against |
226
+ | `--headless` | flag | `False` | Run in headless mode (no UI) |
227
+ | `--slowmo` | milliseconds | `0` | Slow down operations for debugging |
228
+ | `-n` | number | `1` | Number of parallel workers (requires pytest-xdist) |
143
229
 
144
- **Note:** Tests use `podman` to manage containers. Please install it before running.
145
230
 
146
- It's possible to run tests in parallel to speed up the execution. Make sure that you have **xdist** python plugin installed.
231
+ #### Debugging Tests
147
232
 
148
- Use `-n` key to specify a number
149
- of workers:
233
+ When debugging, it's helpful to:
234
+ 1. Run tests in **headed mode** (without `--headless`) to see browser interactions
235
+ 2. Use `--slowmo` to slow down actions and observe what's happening
236
+ 3. Run a single test file or test function instead of the entire suite
237
+ 4. Reduce parallelism (`-n 1` or remove `-n` flag) to avoid race conditions
150
238
 
151
239
  ```bash
152
- pytest --browser-name firefox --pf-version v5 -n 2 -vv
240
+ # Debug specific test with visible browser and slow execution
241
+ pytest testing/components/test_modal.py::test_modal_basic -v --slowmo 1000
153
242
  ```
@@ -1,12 +1,12 @@
1
1
  widgetastic_patternfly5/__init__.py,sha256=Yv3sZRQsi__Wjp5K5odVRZFr6-UQsPLL6lk6g6GWZIg,3822
2
2
  widgetastic_patternfly5/ouia.py,sha256=ZAUgIka9odt4yCKQxD2R0fZ30hZKPtOAL-Fjw2xIGS0,4046
3
3
  widgetastic_patternfly5/charts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- widgetastic_patternfly5/charts/alerts_timeline_chart.py,sha256=U7hJvxAG1Wgj9WFt5QxAmLh9cJm2qzs2YHs2wfEW0Pk,2377
4
+ widgetastic_patternfly5/charts/alerts_timeline_chart.py,sha256=7oAs2JX9ZLH7bYpbz3LMsRlHrwIdx1T6SKpFMd0Jw8U,2474
5
5
  widgetastic_patternfly5/charts/boxplot_chart.py,sha256=6Ek6gwbmwNeazy-5o2fVfvkjpd6bhbTyitiPomvLBts,405
6
- widgetastic_patternfly5/charts/bullet_chart.py,sha256=zfAdvxzPFqeGFzDoqJIUxwkERUlHfPohHOjzPJEjVdw,3680
7
- widgetastic_patternfly5/charts/donut_chart.py,sha256=RmDKyWjhKnGWQwZ1twPN837LYuvlF1inQlYhIgJgFxU,2570
8
- widgetastic_patternfly5/charts/legend.py,sha256=nLv2dEXTICsIT3PJfYn4Ko2WvvgFFggaXVedRTZ3NkE,3057
9
- widgetastic_patternfly5/charts/line_chart.py,sha256=9gjoUqWopSYTng0iRpvppm-CL-QA8H_HpTvFddg0sB4,3995
6
+ widgetastic_patternfly5/charts/bullet_chart.py,sha256=BLAtgqVvIrE3tAGQf3Labp7c5MrQXBW_FAoIWCyLJz4,3830
7
+ widgetastic_patternfly5/charts/donut_chart.py,sha256=ilDQR8ErGp4NqfkbHyvzIKKYVADk5rZkHy9U_BaT2Os,2750
8
+ widgetastic_patternfly5/charts/legend.py,sha256=C5XBUKICsWS_zudVSMbDoKrLuWOqMIAqnYtshnEuKac,3068
9
+ widgetastic_patternfly5/charts/line_chart.py,sha256=ihwbO75pGMZgqBT16gT5e3NgtrmDNl2krRZJjK4iD0w,4044
10
10
  widgetastic_patternfly5/charts/pie_chart.py,sha256=rIxMv6vPERj9FnTp8daejL7MxUTKFCTj9057C1KuuKw,218
11
11
  widgetastic_patternfly5/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  widgetastic_patternfly5/components/alert.py,sha256=8wF4348n7Yp6BnOlbDNGw4_fFKyyjzgpQDOe9a96EsI,2579
@@ -14,33 +14,33 @@ widgetastic_patternfly5/components/breadcrumb.py,sha256=_lQ7pqmh2P9FSwMae_quUTte
14
14
  widgetastic_patternfly5/components/button.py,sha256=dduqxjnTHsHLkPO9TjbWO4HUfpiObMM4yuq3_q8XlA0,3539
15
15
  widgetastic_patternfly5/components/card.py,sha256=YScmzLsxO72ITZFz9u3vyxpD2r0QAzP4yaBmJD9FcZE,2677
16
16
  widgetastic_patternfly5/components/chip.py,sha256=t5_9oTJ__9ccwrCMzVYllDFY7oyJcMwwWKbr3jiIK0w,11218
17
- widgetastic_patternfly5/components/clipboard_copy.py,sha256=r9RESokgZ7s5C2_ByOGLqfIjUx15-ZzFi5usSTdb-ac,1292
17
+ widgetastic_patternfly5/components/clipboard_copy.py,sha256=GgZnhwSLzOZ7gIyODqpjvuM588AnYfL7vaWShzg9wh4,1235
18
18
  widgetastic_patternfly5/components/description_list.py,sha256=sVhKwKnvJguZh9FqyPC1NU2UA_w7uA3ugkY5eoVRlPk,989
19
19
  widgetastic_patternfly5/components/drawer.py,sha256=TKunpAtEyTZSSuOgL5y--lbuUcj5Eiqrm60ICi9O620,1172
20
- widgetastic_patternfly5/components/dual_list_selector.py,sha256=jL1SOfnR5z_rWniN-KU9a26n-0HxBcL2pJbBcOFT2b8,4839
20
+ widgetastic_patternfly5/components/dual_list_selector.py,sha256=MJtEtte5CqEsT3yky04m4W1zKMT7_rl0Ncr1vc6UbHA,4825
21
21
  widgetastic_patternfly5/components/expandable_section.py,sha256=QCqonn1kiYfL-mGcjwrG6x4TiJBr9RKXZvyDerVTUmQ,1876
22
22
  widgetastic_patternfly5/components/modal.py,sha256=uaOsMxWvgCKrF61YX5Dh9z4YCtB0uheQdQTF6cMswxY,2000
23
- widgetastic_patternfly5/components/navigation.py,sha256=uaAc-FoOdYt0xiH_oPK564KjztQLEusk3LhhHSX4PQ4,5333
24
- widgetastic_patternfly5/components/pagination.py,sha256=rumYxlMJcO7F0uQtIU24oANwi6JHHzDkD-YMo-eyltA,8707
23
+ widgetastic_patternfly5/components/navigation.py,sha256=0E8MvWwJY9uTNxG2Z4OUGeP0Ac33sXa-Ls_jPgufF20,5257
24
+ widgetastic_patternfly5/components/pagination.py,sha256=md-OxLeTUzfbnZtA_rY--7UeOYy2wpak74zndLMmJ6M,8649
25
25
  widgetastic_patternfly5/components/popover.py,sha256=_XZlM4v8gsmen73DoSpylMXAMeEkdkjRsuBIIKHHhtE,2242
26
26
  widgetastic_patternfly5/components/progress.py,sha256=UXNjRgy7aW0YerRWR6cwMF-9AjitI6LO8qlOdz0dYW8,1391
27
- widgetastic_patternfly5/components/slider.py,sha256=B9gr8mnndLWivSlS9lIRd3SW6W5Iev9ESnQE9b0A55Y,3128
28
- widgetastic_patternfly5/components/switch.py,sha256=7-ezYAQ3T_cz70GBIW78vTlVaDmRGRhNgi1hWg14Ig8,2226
29
- widgetastic_patternfly5/components/table.py,sha256=fA5LM7buASXOH3z32t1D5ZkFXyMa3ZZmzFJ6kjF73ZQ,17183
27
+ widgetastic_patternfly5/components/slider.py,sha256=icg5VoLPospL6qPyT0XbTdBqEHqDTTw_3YqJBP-pqws,3095
28
+ widgetastic_patternfly5/components/switch.py,sha256=bZWDdeYY6Ub9NSfeYxkpdidajRmF20OyqqF5kQ1j5ws,2281
29
+ widgetastic_patternfly5/components/table.py,sha256=UzOy8v0G9UoU5kNBT9TyxIqfJxcXT8xK0emQJVlI2jg,17180
30
30
  widgetastic_patternfly5/components/tabs.py,sha256=tSuS6x3_rnc5z8yUW_75ZtkdFOzzWPMmMa48VEEZWQk,2260
31
- widgetastic_patternfly5/components/title.py,sha256=bIUzFaptG2i84CF4-AMfjbmdVeKYR32wkOBH9P6V3XQ,920
32
- widgetastic_patternfly5/components/date_and_time/calendar_month.py,sha256=WwpfHWei2GsWxqcvwys9DbxSbzICTBsU2VLgkaqrVns,3386
31
+ widgetastic_patternfly5/components/title.py,sha256=WdxoiSaTyQHNmLru1pSp20cgZhiIxTu1u6syF0r4hJs,867
32
+ widgetastic_patternfly5/components/date_and_time/calendar_month.py,sha256=HTRStubVRVc8Z2aMfMJEw5vwgkVWuK-r6Alwk0fxsFU,3228
33
33
  widgetastic_patternfly5/components/forms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- widgetastic_patternfly5/components/forms/form_select.py,sha256=ht_go5lOXhMzq_FyLxS1ny9jaiBkRI6K_5vMv1DASaw,3531
34
+ widgetastic_patternfly5/components/forms/form_select.py,sha256=CQpwoicDKsUJo9TwrFV1bZu-x5OFhLSMWZYrqynfgqw,3363
35
35
  widgetastic_patternfly5/components/forms/radio.py,sha256=d1fpzoicgSDgoV8K9bHlXFvHEjH3zw59t_WbxTGZyQ8,2009
36
36
  widgetastic_patternfly5/components/menus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  widgetastic_patternfly5/components/menus/context_selector.py,sha256=Bqj8MrxJypLfjcqrrKQj9jfFWHeSWbHIVcH2aA6sRQ8,1156
38
- widgetastic_patternfly5/components/menus/dropdown.py,sha256=uP1bUVqc7b2FxnVHJ7SO8lD-zTlZOz1WQp5GdGFCBt8,10472
39
- widgetastic_patternfly5/components/menus/menu.py,sha256=sRkb46XWMNGueDCkOjs4vyb9Ehjiru71DY3MD2qsorM,8138
38
+ widgetastic_patternfly5/components/menus/dropdown.py,sha256=783reAUtlcKmQCo-nQ-8DjLN4cTgznzly2eftHzlOkY,10259
39
+ widgetastic_patternfly5/components/menus/menu.py,sha256=an0iXBhrswn6rK_FXJckgG0r7B8kv2nMmYPcfHSTiDY,8137
40
40
  widgetastic_patternfly5/components/menus/menu_toggle.py,sha256=PBwNvg6E2RPdCG6ALBUqyTZxCwThJgk5S-9ghTiZ9hM,1029
41
41
  widgetastic_patternfly5/components/menus/options_menu.py,sha256=-7pRukhgxln0rMV77VhNyQ-njLPAE6Wnp2vNAoJUXZk,1474
42
- widgetastic_patternfly5/components/menus/select.py,sha256=X8DApXg_7tLIOulpmEP-f09bDShYL8jcEb1tYvNHwrE,6482
43
- widgetastic.patternfly5-25.7.30.0.dist-info/METADATA,sha256=K5-Y-AiWsKPfFl7Yr21erTcYeGA4YMH_0fiSqxPQmOg,6910
44
- widgetastic.patternfly5-25.7.30.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
- widgetastic.patternfly5-25.7.30.0.dist-info/licenses/LICENSE,sha256=nDhhj8jp0XsTdmvWpTWFpOKVn0LPXPb6ecA9zFF3Exk,576
46
- widgetastic.patternfly5-25.7.30.0.dist-info/RECORD,,
42
+ widgetastic_patternfly5/components/menus/select.py,sha256=GxuD4RH1me7UAy5Zy1rEeUlTA1kL1ginil-TqUKmLOE,6481
43
+ widgetastic_patternfly5-25.12.15.0a1.dist-info/METADATA,sha256=Qt2m5azr0RdnA6j41YqwFhRtZnuZmzHljB3wMuMzAjs,9371
44
+ widgetastic_patternfly5-25.12.15.0a1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
45
+ widgetastic_patternfly5-25.12.15.0a1.dist-info/licenses/LICENSE,sha256=nDhhj8jp0XsTdmvWpTWFpOKVn0LPXPb6ecA9zFF3Exk,576
46
+ widgetastic_patternfly5-25.12.15.0a1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any