widgetastic.patternfly5 25.2.10.0__py3-none-any.whl → 25.2.17.1__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.
Files changed (33) hide show
  1. {widgetastic.patternfly5-25.2.10.0.dist-info → widgetastic.patternfly5-25.2.17.1.dist-info}/METADATA +9 -12
  2. widgetastic.patternfly5-25.2.17.1.dist-info/RECORD +46 -0
  3. widgetastic_patternfly5/__init__.py +39 -50
  4. widgetastic_patternfly5/charts/alerts_timeline_chart.py +65 -0
  5. widgetastic_patternfly5/charts/bullet_chart.py +3 -5
  6. widgetastic_patternfly5/charts/donut_chart.py +2 -6
  7. widgetastic_patternfly5/charts/line_chart.py +13 -4
  8. widgetastic_patternfly5/components/alert.py +5 -6
  9. widgetastic_patternfly5/components/button.py +7 -8
  10. widgetastic_patternfly5/components/card.py +3 -10
  11. widgetastic_patternfly5/components/chip.py +11 -14
  12. widgetastic_patternfly5/components/clipboard_copy.py +1 -3
  13. widgetastic_patternfly5/components/drawer.py +7 -3
  14. widgetastic_patternfly5/components/dual_list_selector.py +1 -2
  15. widgetastic_patternfly5/components/forms/form_select.py +4 -8
  16. widgetastic_patternfly5/components/forms/radio.py +1 -4
  17. widgetastic_patternfly5/components/menus/context_selector.py +2 -2
  18. widgetastic_patternfly5/components/menus/dropdown.py +38 -13
  19. widgetastic_patternfly5/components/menus/menu.py +19 -18
  20. widgetastic_patternfly5/components/menus/options_menu.py +4 -3
  21. widgetastic_patternfly5/components/menus/select.py +14 -18
  22. widgetastic_patternfly5/components/modal.py +1 -1
  23. widgetastic_patternfly5/components/navigation.py +3 -3
  24. widgetastic_patternfly5/components/pagination.py +3 -8
  25. widgetastic_patternfly5/components/progress.py +2 -3
  26. widgetastic_patternfly5/components/switch.py +8 -7
  27. widgetastic_patternfly5/components/table.py +21 -16
  28. widgetastic_patternfly5/components/tabs.py +7 -4
  29. widgetastic_patternfly5/components/title.py +1 -2
  30. widgetastic_patternfly5/ouia.py +5 -10
  31. widgetastic.patternfly5-25.2.10.0.dist-info/RECORD +0 -45
  32. {widgetastic.patternfly5-25.2.10.0.dist-info → widgetastic.patternfly5-25.2.17.1.dist-info}/WHEEL +0 -0
  33. {widgetastic.patternfly5-25.2.10.0.dist-info → widgetastic.patternfly5-25.2.17.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,7 @@
1
1
  from contextlib import contextmanager
2
2
 
3
- from wait_for import wait_for_decorator
4
- from widgetastic.exceptions import NoSuchElementException
5
- from widgetastic.exceptions import UnexpectedAlertPresentException
3
+ from cached_property import cached_property
4
+ from widgetastic.exceptions import NoSuchElementException, UnexpectedAlertPresentException
6
5
  from widgetastic.utils import ParametrizedLocator
7
6
  from widgetastic.widget import Widget
8
7
  from widgetastic.xpath import quote
@@ -41,6 +40,13 @@ class BaseDropdown:
41
40
  "contains(@class, '-c-dropdown__menu-item')) and normalize-space(.)={}]"
42
41
  )
43
42
 
43
+ @cached_property
44
+ def is_pf6(self):
45
+ """In PF-v6, item selection requires the use of the root_browser, as the item locators are
46
+ located outside the ROOT.
47
+ """
48
+ return True if self.browser.elements(".//*[contains(@class, 'pf-v6')]") else False
49
+
44
50
  @contextmanager
45
51
  def opened(self):
46
52
  """A context manager to open and then close a Dropdown."""
@@ -77,10 +83,13 @@ class BaseDropdown:
77
83
  if self.is_open:
78
84
  return
79
85
 
80
- @wait_for_decorator(timeout=3)
81
- def _click():
82
- self.browser.click(self.BUTTON_LOCATOR)
83
- return self.is_open
86
+ # @wait_for_decorator(timeout=3)
87
+ # def _click():
88
+ # self.browser.click(self.BUTTON_LOCATOR)
89
+ # return self.is_open
90
+ el = self.browser.wait_for_element(self.BUTTON_LOCATOR)
91
+ self.browser.click(el)
92
+ return self.is_open
84
93
 
85
94
  def close(self, ignore_nonpresent=False):
86
95
  """Close the dropdown
@@ -102,7 +111,10 @@ class BaseDropdown:
102
111
  def items(self):
103
112
  """Returns a list of all dropdown items as strings."""
104
113
  with self.opened():
105
- result = [self.browser.text(el) for el in self.browser.elements(self.ITEMS_LOCATOR)]
114
+ items_element = self.browser.elements(self.ITEMS_LOCATOR) or self.root_browser.elements(
115
+ self.ITEMS_LOCATOR
116
+ )
117
+ result = [self.browser.text(el) for el in items_element]
106
118
  return result
107
119
 
108
120
  def has_item(self, item):
@@ -120,7 +132,11 @@ class BaseDropdown:
120
132
  """Returns a WebElement for given item name."""
121
133
  try:
122
134
  self.open()
123
- result = self.browser.element(self.ITEM_LOCATOR.format(quote(item)), **kwargs)
135
+ result = (
136
+ self.root_browser.element(self.ITEM_LOCATOR.format(quote(item)), **kwargs)
137
+ if self.is_pf6
138
+ else self.browser.element(self.ITEM_LOCATOR.format(quote(item)), **kwargs)
139
+ )
124
140
  if close:
125
141
  self.close()
126
142
  return result
@@ -133,7 +149,7 @@ class BaseDropdown:
133
149
  items_string = "These items are present: {}".format("; ".join(items))
134
150
  else:
135
151
  items_string = "The dropdown is probably not present"
136
- raise DropdownItemNotFound("Item {!r} not found. {}".format(item, items_string))
152
+ raise DropdownItemNotFound(f"Item {item!r} not found. {items_string}")
137
153
 
138
154
  def item_enabled(self, item, close=True, **kwargs):
139
155
  """Returns whether the given item is enabled.
@@ -243,7 +259,10 @@ class BaseGroupDropdown:
243
259
  def groups(self):
244
260
  """Returns a list of all group names as strings."""
245
261
  with self.opened():
246
- result = [self.browser.text(el) for el in self.browser.elements(self.GROUPS_LOCATOR)]
262
+ groups_element = self.browser.elements(
263
+ self.GROUPS_LOCATOR
264
+ ) or self.root_browser.elements(self.GROUPS_LOCATOR)
265
+ result = [self.browser.text(el) for el in groups_element]
247
266
  return result
248
267
 
249
268
  def item_element(self, item, group_name=None, close=True):
@@ -251,13 +270,19 @@ class BaseGroupDropdown:
251
270
  self.open()
252
271
  try:
253
272
  kwargs = (
254
- {"parent": self.browser.element(self.GROUP_LOCATOR.format(quote(group_name)))}
273
+ {
274
+ "parent": (
275
+ self.root_browser.element(self.GROUP_LOCATOR.format(quote(group_name)))
276
+ if self.is_pf6
277
+ else self.browser.element(self.GROUP_LOCATOR.format(quote(group_name)))
278
+ )
279
+ }
255
280
  if group_name
256
281
  else {}
257
282
  )
258
283
  except NoSuchElementException:
259
284
  raise DropdownItemNotFound(
260
- 'Following group "{}" not found. Available are: {}'.format(group_name, self.groups)
285
+ f'Following group "{group_name}" not found. Available are: {self.groups}'
261
286
  )
262
287
  return super().item_element(item, close=close, **kwargs)
263
288
 
@@ -1,8 +1,6 @@
1
1
  from widgetastic.exceptions import NoSuchElementException
2
2
 
3
- from .dropdown import Dropdown
4
- from .dropdown import DropdownItemDisabled
5
- from .dropdown import DropdownItemNotFound
3
+ from .dropdown import Dropdown, DropdownItemDisabled, DropdownItemNotFound
6
4
 
7
5
 
8
6
  class MenuItemDisabled(DropdownItemDisabled):
@@ -48,9 +46,10 @@ class BaseMenu:
48
46
  def selected_items(self):
49
47
  """Returns a list of all selected items as strings."""
50
48
  with self.opened():
51
- result = [
52
- self.browser.text(el) for el in self.browser.elements(self.SELECTED_ITEMS_LOCATOR)
53
- ]
49
+ items_element = self.browser.elements(
50
+ self.SELECTED_ITEMS_LOCATOR
51
+ ) or self.root_browser.elements(self.SELECTED_ITEMS_LOCATOR)
52
+ result = [self.browser.text(el) for el in items_element]
54
53
  return result
55
54
 
56
55
  @property
@@ -71,7 +70,7 @@ class BaseMenu:
71
70
  def close(self, ignore_nonpresent=False):
72
71
  """Close the menu
73
72
 
74
- It it is always open we do nothing
73
+ It is always open we do nothing
75
74
 
76
75
  Args:
77
76
  ignore_nonpresent: Will ignore exceptions due to disabled or missing dropdown
@@ -95,9 +94,7 @@ class BaseMenu:
95
94
  return super().item_element(item, close)
96
95
  except DropdownItemNotFound:
97
96
  raise MenuItemNotFound(
98
- "Item {!r} not found in {}. Available items: {}".format(
99
- item, repr(self), self.items
100
- )
97
+ f"Item {item!r} not found in {repr(self)}. Available items: {self.items}"
101
98
  )
102
99
 
103
100
  def item_select(self, item):
@@ -113,10 +110,8 @@ class BaseMenu:
113
110
  return super().item_select(item)
114
111
  except DropdownItemDisabled:
115
112
  raise MenuItemDisabled(
116
- 'Item "{}" of {} is disabled\n'
117
- "The following items are available and enabled: {}".format(
118
- item, repr(self), self.enabled_items
119
- )
113
+ f'Item "{item}" of {repr(self)} is disabled\n'
114
+ f"The following items are available and enabled: {self.enabled_items}"
120
115
  )
121
116
 
122
117
  def fill(self, value):
@@ -153,7 +148,7 @@ class BaseCheckboxMenu(BaseMenu):
153
148
  item: Item to be selected
154
149
  close: Close the dropdown when finished
155
150
  """
156
- if not isinstance(items, (list, tuple, set)):
151
+ if not isinstance(items, list | tuple | set):
157
152
  items = [items]
158
153
 
159
154
  try:
@@ -172,7 +167,7 @@ class BaseCheckboxMenu(BaseMenu):
172
167
  item: Item to be selected
173
168
  close: Close the dropdown when finished
174
169
  """
175
- if not isinstance(items, (list, tuple, set)):
170
+ if not isinstance(items, list | tuple | set):
176
171
  items = [items]
177
172
 
178
173
  try:
@@ -204,7 +199,10 @@ class BaseCheckboxMenu(BaseMenu):
204
199
  """Returns a dictionary containing the selected status as bools."""
205
200
  selected = {}
206
201
  with self.opened():
207
- for el in self.browser.elements(self.ITEMS_LOCATOR):
202
+ item_elements = self.browser.elements(self.ITEMS_LOCATOR) or self.root_browser.elements(
203
+ self.ITEMS_LOCATOR
204
+ )
205
+ for el in item_elements:
208
206
  item = self.browser.text(el)
209
207
  try:
210
208
  # get the child element of the label
@@ -223,7 +221,10 @@ class BaseCheckboxMenu(BaseMenu):
223
221
  close: Close the dropdown when finished
224
222
  """
225
223
  self.open()
226
- result = [self.browser.text(el) for el in self.browser.elements(self.ITEMS_LOCATOR)]
224
+ item_elements = self.browser.elements(self.ITEMS_LOCATOR) or self.root_browser.elements(
225
+ self.ITEMS_LOCATOR
226
+ )
227
+ result = [self.browser.text(el) for el in item_elements]
227
228
 
228
229
  if close:
229
230
  self.close()
@@ -30,9 +30,10 @@ class BaseOptionsMenu:
30
30
  def selected_items(self):
31
31
  """Returns a list of all selected items in the options menu."""
32
32
  with self.opened():
33
- return [
34
- self.browser.text(el) for el in self.browser.elements(self.SELECTED_ITEMS_LOCATOR)
35
- ]
33
+ selected_items_elements = self.browser.elements(
34
+ self.SELECTED_ITEMS_LOCATOR
35
+ ) or self.root_browser.elements(self.SELECTED_ITEMS_LOCATOR)
36
+ return [self.browser.text(el) for el in selected_items_elements]
36
37
 
37
38
 
38
39
  class OptionsMenu(BaseOptionsMenu, Dropdown):
@@ -1,9 +1,7 @@
1
1
  from widgetastic.exceptions import NoSuchElementException
2
2
  from widgetastic.widget import TextInput
3
3
 
4
- from .dropdown import Dropdown
5
- from .dropdown import DropdownItemDisabled
6
- from .dropdown import DropdownItemNotFound
4
+ from .dropdown import Dropdown, DropdownItemDisabled, DropdownItemNotFound
7
5
 
8
6
 
9
7
  class SelectItemDisabled(DropdownItemDisabled):
@@ -40,9 +38,7 @@ class BaseSelect:
40
38
  return super().item_element(item, close)
41
39
  except DropdownItemNotFound:
42
40
  raise SelectItemNotFound(
43
- "Item {!r} not found in {}. Available items: {}".format(
44
- item, repr(self), self.items
45
- )
41
+ f"Item {item!r} not found in {repr(self)}. Available items: {self.items}"
46
42
  )
47
43
 
48
44
  def item_select(self, item):
@@ -57,12 +53,7 @@ class BaseSelect:
57
53
  try:
58
54
  return super().item_select(item)
59
55
  except DropdownItemDisabled:
60
- raise SelectItemDisabled(
61
- 'Item "{}" of {} is disabled\n'
62
- "The following items are available and enabled: {}".format(
63
- item, repr(self), self.enabled_items
64
- )
65
- )
56
+ raise SelectItemDisabled('Item "{}" of {} is disabled')
66
57
 
67
58
  def fill(self, value):
68
59
  """Fills a Select with a value."""
@@ -90,8 +81,7 @@ class BaseCheckboxSelect(BaseSelect):
90
81
  ".//*[contains(@class, '-c-menu__list-item') or contains(@class, '-c-select__menu-item')]"
91
82
  )
92
83
  ITEM_LOCATOR = (
93
- f"{ITEMS_LOCATOR}[.//span[starts-with(normalize-space(.), {{}})]]"
94
- f"//input[@type='checkbox']"
84
+ f"{ITEMS_LOCATOR}[.//span[starts-with(normalize-space(.), {{}})]]//input[@type='checkbox']"
95
85
  )
96
86
 
97
87
  def item_select(self, items, close=True):
@@ -101,7 +91,7 @@ class BaseCheckboxSelect(BaseSelect):
101
91
  item: Item to be selected
102
92
  close: Close the dropdown when finished
103
93
  """
104
- if not isinstance(items, (list, tuple, set)):
94
+ if not isinstance(items, list | tuple | set):
105
95
  items = [items]
106
96
 
107
97
  try:
@@ -120,7 +110,7 @@ class BaseCheckboxSelect(BaseSelect):
120
110
  item: Item to be selected
121
111
  close: Close the dropdown when finished
122
112
  """
123
- if not isinstance(items, (list, tuple, set)):
113
+ if not isinstance(items, list | tuple | set):
124
114
  items = [items]
125
115
 
126
116
  try:
@@ -158,7 +148,10 @@ class BaseCheckboxSelect(BaseSelect):
158
148
  """Returns a dictionary containing the selected status as bools."""
159
149
  selected = {}
160
150
  with self.opened():
161
- for el in self.browser.elements(self.ITEMS_LOCATOR):
151
+ items_element = self.browser.elements(self.ITEMS_LOCATOR) or self.root_browser.elements(
152
+ self.ITEMS_LOCATOR
153
+ )
154
+ for el in items_element:
162
155
  item = self.browser.text(el)
163
156
  try:
164
157
  # get the child element of the label
@@ -177,7 +170,10 @@ class BaseCheckboxSelect(BaseSelect):
177
170
  close: Close the dropdown when finished
178
171
  """
179
172
  self.open()
180
- result = [self.browser.text(el) for el in self.browser.elements(self.ITEMS_LOCATOR)]
173
+ items_element = self.browser.elements(self.ITEMS_LOCATOR) or self.root_browser.elements(
174
+ self.ITEMS_LOCATOR
175
+ )
176
+ result = [self.browser.text(el) for el in items_element]
181
177
 
182
178
  if close:
183
179
  self.close()
@@ -16,7 +16,7 @@ class BaseModal:
16
16
 
17
17
  BODY = ".//div[contains(@class, '-c-modal-box__body')]"
18
18
  FOOTER = ".//*[contains(@class, '-c-modal-box__footer')]/child::node()"
19
- FOOTER_ITEM = ".//*[contains(@class, '-c-modal-box__footer')]" "/button[normalize-space(.)={}]"
19
+ FOOTER_ITEM = ".//*[contains(@class, '-c-modal-box__footer')]/button[normalize-space(.)={}]"
20
20
  TITLE = ".//h1[contains(@class, '-c-title') or contains(@class, '-c-modal-box__title')]"
21
21
  CLOSE = ".//button[@aria-label='Close']"
22
22
 
@@ -126,7 +126,7 @@ class BaseNavigation:
126
126
  current_item = self.browser.element(self.SUB_ITEMS_ROOT, parent=li)
127
127
 
128
128
  def __repr__(self):
129
- return "{}({!r})".format(type(self).__name__, self.ROOT)
129
+ return f"{type(self).__name__}({self.ROOT!r})"
130
130
 
131
131
 
132
132
  class Navigation(BaseNavigation, Widget):
@@ -138,11 +138,11 @@ class Navigation(BaseNavigation, Widget):
138
138
 
139
139
  quoted_label = quote(label) if label else ""
140
140
  if label:
141
- label_part = " and @label={} or @aria-label={}".format(quoted_label, quoted_label)
141
+ label_part = f" and @label={quoted_label} or @aria-label={quoted_label}"
142
142
  else:
143
143
  label_part = ""
144
144
 
145
- id_part = " and @id={}".format(quote(id)) if id else ""
145
+ id_part = f" and @id={quote(id)}" if id else ""
146
146
  if locator is not None:
147
147
  self.locator = locator
148
148
  elif label_part or id_part:
@@ -3,10 +3,7 @@ from contextlib import contextmanager
3
3
 
4
4
  from selenium.webdriver.common.keys import Keys
5
5
  from widgetastic.utils import ParametrizedLocator
6
- from widgetastic.widget import GenericLocatorWidget
7
- from widgetastic.widget import Text
8
- from widgetastic.widget import TextInput
9
- from widgetastic.widget import View
6
+ from widgetastic.widget import GenericLocatorWidget, Text, TextInput, View
10
7
 
11
8
  from .menus.options_menu import OptionsMenu
12
9
 
@@ -169,16 +166,14 @@ class BasePagination:
169
166
  def set_per_page(self, count):
170
167
  """Sets the number of items per page. (Will cast to str)"""
171
168
  value = str(count)
172
- value_per_page = "{} per page".format(value)
169
+ value_per_page = f"{value} per page"
173
170
  items = self._options.items
174
171
  if value_per_page in items:
175
172
  self._options.item_select(value_per_page)
176
173
  elif value in items:
177
174
  self._options.item_select(value)
178
175
  else:
179
- raise ValueError(
180
- "count '{}' is not a valid option in the pagination dropdown".format(count)
181
- )
176
+ raise ValueError(f"count '{count}' is not a valid option in the pagination dropdown")
182
177
 
183
178
  def go_to_page(self, value):
184
179
  """Navigate to custom page number."""
@@ -32,9 +32,8 @@ class BaseProgress:
32
32
  for class_ in self.browser.classes(self):
33
33
  if class_ in self.STATUS_TYPE_MAPPING:
34
34
  return self.STATUS_TYPE_MAPPING[class_]
35
- else:
36
- default_alert_type = "info"
37
- return default_alert_type
35
+ default_alert_type = "info"
36
+ return default_alert_type
38
37
 
39
38
 
40
39
  class Progress(BaseProgress, Widget):
@@ -13,8 +13,9 @@ class BaseSwitch:
13
13
  """
14
14
 
15
15
  CHECKBOX_LOCATOR = "./input"
16
- LABEL_ON = "./span[contains(@class, 'pf-m-on')]"
17
- LABEL_OFF = "./span[contains(@class, 'pf-m-off')]"
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')]"
18
19
 
19
20
  @property
20
21
  def is_enabled(self):
@@ -24,7 +25,7 @@ class BaseSwitch:
24
25
  def click(self):
25
26
  """Click on a Switch."""
26
27
  if not self.is_enabled:
27
- raise SwitchDisabled("{} is disabled".format(repr(self)))
28
+ raise SwitchDisabled(f"{repr(self)} is disabled")
28
29
  else:
29
30
  self.browser.click(self.CHECKBOX_LOCATOR)
30
31
  return True
@@ -37,7 +38,7 @@ class BaseSwitch:
37
38
  def fill(self, value):
38
39
  """Fills a Switch with the supplied value."""
39
40
  if not self.is_enabled:
40
- raise SwitchDisabled("{} is disabled".format(repr(self)))
41
+ raise SwitchDisabled(f"{repr(self)} is disabled")
41
42
  if bool(value) == self.selected:
42
43
  return False
43
44
  else:
@@ -48,9 +49,9 @@ class BaseSwitch:
48
49
  def label(self):
49
50
  """Returns the label of the Switch."""
50
51
  if self.selected:
51
- return self._read_locator(self.LABEL_ON)
52
+ return self._read_locator(self.PF_5_LABEL_ON) or self._read_locator(self.PF_6_LABLE)
52
53
  else:
53
- return self._read_locator(self.LABEL_OFF)
54
+ return self._read_locator(self.PF_5_LABEL_OFF) or self._read_locator(self.PF_6_LABLE)
54
55
 
55
56
  def read(self):
56
57
  """Returns a boolean detailing if the Switch is on (True) of off (False)."""
@@ -63,7 +64,7 @@ class BaseSwitch:
63
64
  return None
64
65
 
65
66
  def __repr__(self):
66
- return "{}({!r})".format(type(self).__name__, self.locator)
67
+ return f"{type(self).__name__}({self.locator!r})"
67
68
 
68
69
 
69
70
  class Switch(BaseSwitch, GenericLocatorWidget):
@@ -1,23 +1,23 @@
1
1
  from selenium.common.exceptions import NoSuchElementException
2
2
  from widgetastic.log import create_item_logger
3
- from widgetastic.widget import Table
4
- from widgetastic.widget import TableColumn
5
- from widgetastic.widget import TableRow
6
- from widgetastic.widget import Text
7
- from widgetastic.widget import Widget
3
+ from widgetastic.widget import Table, TableColumn, TableRow, Text, Widget
8
4
  from widgetastic.widget.table import resolve_table_widget
9
5
 
10
6
 
11
7
  class HeaderColumn(TableColumn):
12
8
  """Represents a cell in the header row."""
13
9
 
10
+ sort_indicator = Text(
11
+ locator=".//span[contains(@class, '-c-table__sort-indicator')] | .//button[contains(@class, '-c-table__button')]"
12
+ )
13
+
14
14
  def __locator__(self):
15
- return "(./td|./th)[{}]".format(self.position + 1)
15
+ return f"(./td|./th)[{self.position + 1}]"
16
16
 
17
17
  @property
18
18
  def is_sortable(self):
19
19
  """Returns true of the column is sortable."""
20
- return "-c-table__sort" in self.browser.classes(self)
20
+ return any(cls for cls in self.browser.classes(self) if "-c-table__sort" in cls)
21
21
 
22
22
  @property
23
23
  def sorting_order(self):
@@ -28,10 +28,14 @@ class HeaderColumn(TableColumn):
28
28
  """Sorts the column according to the supplied "ascending" or "descending"."""
29
29
  if order not in ("ascending", "descending"):
30
30
  raise ValueError("order should be either 'ascending' or 'descending'")
31
+
32
+ if not self.is_sortable:
33
+ raise ValueError("Column is not sortable.")
34
+
31
35
  for _ in range(10):
32
36
  if self.sorting_order == order:
33
37
  break
34
- self.click()
38
+ self.sort_indicator.click()
35
39
  else:
36
40
  raise AssertionError(f"Could not set sorting order to `{order}` after 10 tries.")
37
41
 
@@ -46,7 +50,7 @@ class HeaderRow(TableRow):
46
50
  return "./thead/tr"
47
51
 
48
52
  def __repr__(self):
49
- return "{}({!r})".format(type(self).__name__, self.parent)
53
+ return f"{type(self).__name__}({self.parent!r})"
50
54
 
51
55
  def __getitem__(self, item):
52
56
  if isinstance(item, str):
@@ -149,7 +153,7 @@ class ExpandableTableHeaderColumn(TableColumn):
149
153
 
150
154
  def __locator__(self):
151
155
  """Override the locator to look inside the first 'tr' within the tbody"""
152
- return "./tr[1]/th[{}]".format(self.position + 1)
156
+ return f"./tr[1]/th[{self.position + 1}]"
153
157
 
154
158
 
155
159
  class RowNotExpandable(Exception):
@@ -157,7 +161,7 @@ class RowNotExpandable(Exception):
157
161
  self.row = row
158
162
 
159
163
  def __str__(self):
160
- return "Row is not expandable: {}".format(repr(self.row))
164
+ return f"Row is not expandable: {repr(self.row)}"
161
165
 
162
166
 
163
167
  class ColumnNotExpandable(Exception):
@@ -165,7 +169,7 @@ class ColumnNotExpandable(Exception):
165
169
  self.column = column
166
170
 
167
171
  def __str__(self):
168
- return "Column is not expandable: {}".format(repr(self.column))
172
+ return f"Column is not expandable: {repr(self.column)}"
169
173
 
170
174
 
171
175
  class ExpandableTableRow(PatternflyTableRow):
@@ -231,8 +235,9 @@ class ExpandableTableRow(PatternflyTableRow):
231
235
  """Returns a text representation of the table row."""
232
236
  result = super().read()
233
237
  # Remove the column with the "expand" button in it
234
- if 0 in result and not result[0]:
235
- del result[0]
238
+ for expand_col in [0, "Row expansion"]:
239
+ if expand_col in result and not result[expand_col]:
240
+ del result[expand_col]
236
241
  return result
237
242
 
238
243
 
@@ -329,9 +334,9 @@ class ExpandableColumn(TableColumn):
329
334
  """Override the locator to look inside the first 'tr' within the tbody"""
330
335
  if self.position == 0:
331
336
  # we assume the th column is in the first position
332
- return "./tr[1]/th[{}]".format(self.position + 1)
337
+ return f"./tr[1]/th[{self.position + 1}]"
333
338
  else:
334
- return "./tr[1]/td[{}]".format(self.position)
339
+ return f"./tr[1]/td[{self.position}]"
335
340
 
336
341
  @property
337
342
  def is_expandable(self):
@@ -17,7 +17,7 @@ class Tab(View):
17
17
 
18
18
  # Locator of the Tab selector
19
19
  TAB_LOCATOR = ParametrizedLocator(
20
- './/div[contains(@class, "-c-tabs")]/ul' "/li[button[normalize-space(.)={@tab_name|quote}]]"
20
+ './/div[contains(@class, "-c-tabs")]/ul/li[button[normalize-space(.)={@tab_name|quote}]]'
21
21
  )
22
22
 
23
23
  ROOT = ParametrizedLocator(
@@ -44,14 +44,17 @@ class Tab(View):
44
44
 
45
45
  def click(self):
46
46
  """Clicks the tab."""
47
- return self.parent_browser.click(self.TAB_LOCATOR)
47
+ el = self.parent_browser.move_to_element(self.TAB_LOCATOR)
48
+ if tab_btns := self.browser.elements(".//button", parent=el):
49
+ return self.browser.click(tab_btns[0])
50
+ return self.parent_browser.click(el)
48
51
 
49
52
  def select(self):
50
53
  """Selects the tab (checks if active already first)."""
51
54
  if not self.is_active():
52
55
  self.logger.info("Opening the tab %s", self.tab_name)
53
56
 
54
- @wait_for_decorator(timeout=3)
57
+ @wait_for_decorator(timeout=5)
55
58
  def _click():
56
59
  self.click()
57
60
  return self.is_active()
@@ -61,4 +64,4 @@ class Tab(View):
61
64
  self.select()
62
65
 
63
66
  def __repr__(self):
64
- return "<Tab {!r}>".format(self.tab_name)
67
+ return f"<Tab {self.tab_name!r}>"
@@ -1,5 +1,4 @@
1
- from widgetastic.widget import ParametrizedLocator
2
- from widgetastic.widget import Widget
1
+ from widgetastic.widget import ParametrizedLocator, Widget
3
2
 
4
3
 
5
4
  class BaseTitle:
@@ -1,5 +1,4 @@
1
- from widgetastic.ouia import OUIAGenericView
2
- from widgetastic.ouia import OUIAGenericWidget
1
+ from widgetastic.ouia import OUIAGenericView, OUIAGenericWidget
3
2
  from widgetastic.ouia.input import TextInput as BaseOuiaTextInput
4
3
  from widgetastic.ouia.text import Text as BaseOuiaText
5
4
  from widgetastic.widget.table import Table
@@ -12,17 +11,13 @@ from widgetastic_patternfly5.components.card import BaseCard
12
11
  from widgetastic_patternfly5.components.clipboard_copy import BaseClipboardCopy
13
12
  from widgetastic_patternfly5.components.forms.form_select import BaseFormSelect
14
13
  from widgetastic_patternfly5.components.menus.dropdown import BaseDropdown
15
- from widgetastic_patternfly5.components.menus.menu import BaseCheckboxMenu
16
- from widgetastic_patternfly5.components.menus.menu import BaseMenu
17
- from widgetastic_patternfly5.components.menus.select import BaseSelect
18
- from widgetastic_patternfly5.components.menus.select import BaseTypeaheadSelect
14
+ from widgetastic_patternfly5.components.menus.menu import BaseCheckboxMenu, BaseMenu
15
+ from widgetastic_patternfly5.components.menus.select import BaseSelect, BaseTypeaheadSelect
19
16
  from widgetastic_patternfly5.components.modal import BaseModal
20
17
  from widgetastic_patternfly5.components.navigation import BaseNavigation
21
- from widgetastic_patternfly5.components.pagination import BaseCompactPagination
22
- from widgetastic_patternfly5.components.pagination import BasePagination
18
+ from widgetastic_patternfly5.components.pagination import BaseCompactPagination, BasePagination
23
19
  from widgetastic_patternfly5.components.switch import BaseSwitch
24
- from widgetastic_patternfly5.components.table import BaseExpandableTable
25
- from widgetastic_patternfly5.components.table import BasePatternflyTable
20
+ from widgetastic_patternfly5.components.table import BaseExpandableTable, BasePatternflyTable
26
21
  from widgetastic_patternfly5.components.title import BaseTitle
27
22
 
28
23