widgetastic.patternfly5 25.2.11.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 (32) hide show
  1. {widgetastic.patternfly5-25.2.11.0.dist-info → widgetastic.patternfly5-25.2.17.1.dist-info}/METADATA +6 -12
  2. widgetastic.patternfly5-25.2.17.1.dist-info/RECORD +46 -0
  3. widgetastic_patternfly5/__init__.py +39 -50
  4. widgetastic_patternfly5/charts/bullet_chart.py +3 -5
  5. widgetastic_patternfly5/charts/donut_chart.py +2 -6
  6. widgetastic_patternfly5/charts/line_chart.py +13 -4
  7. widgetastic_patternfly5/components/alert.py +5 -6
  8. widgetastic_patternfly5/components/button.py +7 -8
  9. widgetastic_patternfly5/components/card.py +3 -10
  10. widgetastic_patternfly5/components/chip.py +11 -14
  11. widgetastic_patternfly5/components/clipboard_copy.py +1 -3
  12. widgetastic_patternfly5/components/drawer.py +7 -3
  13. widgetastic_patternfly5/components/dual_list_selector.py +1 -2
  14. widgetastic_patternfly5/components/forms/form_select.py +4 -8
  15. widgetastic_patternfly5/components/forms/radio.py +1 -4
  16. widgetastic_patternfly5/components/menus/context_selector.py +2 -2
  17. widgetastic_patternfly5/components/menus/dropdown.py +38 -13
  18. widgetastic_patternfly5/components/menus/menu.py +19 -18
  19. widgetastic_patternfly5/components/menus/options_menu.py +4 -3
  20. widgetastic_patternfly5/components/menus/select.py +13 -12
  21. widgetastic_patternfly5/components/modal.py +1 -1
  22. widgetastic_patternfly5/components/navigation.py +3 -3
  23. widgetastic_patternfly5/components/pagination.py +3 -8
  24. widgetastic_patternfly5/components/progress.py +2 -3
  25. widgetastic_patternfly5/components/switch.py +8 -7
  26. widgetastic_patternfly5/components/table.py +21 -16
  27. widgetastic_patternfly5/components/tabs.py +7 -4
  28. widgetastic_patternfly5/components/title.py +1 -2
  29. widgetastic_patternfly5/ouia.py +5 -10
  30. widgetastic.patternfly5-25.2.11.0.dist-info/RECORD +0 -46
  31. {widgetastic.patternfly5-25.2.11.0.dist-info → widgetastic.patternfly5-25.2.17.1.dist-info}/WHEEL +0 -0
  32. {widgetastic.patternfly5-25.2.11.0.dist-info → widgetastic.patternfly5-25.2.17.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: widgetastic.patternfly5
3
- Version: 25.2.11.0
3
+ Version: 25.2.17.1
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>
@@ -21,16 +21,13 @@ License-File: LICENSE
21
21
  Keywords: patternfly,patternfly5,widgetastic
22
22
  Classifier: Programming Language :: Python
23
23
  Classifier: Programming Language :: Python :: 3
24
- Classifier: Programming Language :: Python :: 3.8
25
- Classifier: Programming Language :: Python :: 3.9
26
- Classifier: Programming Language :: Python :: 3.10
27
24
  Classifier: Programming Language :: Python :: 3.11
28
25
  Classifier: Programming Language :: Python :: 3.12
29
26
  Classifier: Programming Language :: Python :: 3.13
30
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
28
  Classifier: Topic :: Software Development :: Quality Assurance
32
29
  Classifier: Topic :: Software Development :: Testing
33
- Requires-Python: >=3.8
30
+ Requires-Python: >=3.11
34
31
  Requires-Dist: widgetastic-core>=1.0.6
35
32
  Provides-Extra: dev
36
33
  Requires-Dist: codecov; extra == 'dev'
@@ -58,15 +55,12 @@ Description-Content-Type: text/markdown
58
55
  <a href="https://github.com/RedHatQE/widgetastic.patternfly5/actions/workflows/tests.yaml">
59
56
  <img alt="github actions" src="https://github.com/RedHatQE/widgetastic.patternfly5/actions/workflows/tests.yaml/badge.svg">
60
57
  </a>
61
- <a href="https://pypi.org/project/black">
62
- <img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg">
63
- </a>
64
58
  <a href="https://results.pre-commit.ci/latest/github/RedHatQE/widgetastic.patternfly5/main">
65
59
  <img alt="pre: black" src="https://results.pre-commit.ci/badge/github/RedHatQE/widgetastic.patternfly5/main.svg">
66
60
  </a>
67
61
  </p>
68
62
 
69
- This library offers Widgetastic Widgets for [PatternFly v5](https://www.patternfly.org/), serving as an extended
63
+ This library offers Widgetastic Widgets for [PatternFly v5/v6](https://www.patternfly.org/), serving as an extended
70
64
  itteration of [widgetastic.patternfly4](https://github.com/RedHatQE/widgetastic.patternfly4).
71
65
 
72
66
 
@@ -114,7 +108,7 @@ itteration of [widgetastic.patternfly4](https://github.com/RedHatQE/widgetastic.
114
108
  - [pie-chart](https://www.patternfly.org/charts/pie-chart)
115
109
 
116
110
  ### Patterns:
117
- - [card-view](https://patternfly-react-main.surge.sh/patterns/card-view)
111
+ - [card-view](https://www.patternfly.org/patterns/card-view)
118
112
 
119
113
 
120
114
  ### Contribution guide
@@ -138,7 +132,7 @@ pre-commit install
138
132
 
139
133
  ### Testing
140
134
 
141
- The library has selenium tests that are performed against [Patternfly React docs](https://patternfly-react-main.surge.sh).
135
+ 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).
142
136
  It's also configured to run the tests every time when a new version of that page is released.
143
137
 
144
138
  Tests spawn a container from official selenium image - [selenium/standalone-{chrome/firefox}](https://hub.docker.com/u/selenium).
@@ -152,5 +146,5 @@ Use `-n` key to specify a number
152
146
  of workers:
153
147
 
154
148
  ```bash
155
- BROWSER=firefox pytest -v testing -n 4
149
+ pytest --browser-name firefox --pf-version v5 -n 2 -vv
156
150
  ```
@@ -0,0 +1,46 @@
1
+ widgetastic_patternfly5/__init__.py,sha256=Yv3sZRQsi__Wjp5K5odVRZFr6-UQsPLL6lk6g6GWZIg,3822
2
+ widgetastic_patternfly5/ouia.py,sha256=ZAUgIka9odt4yCKQxD2R0fZ30hZKPtOAL-Fjw2xIGS0,4046
3
+ widgetastic_patternfly5/charts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ widgetastic_patternfly5/charts/alerts_timeline_chart.py,sha256=U7hJvxAG1Wgj9WFt5QxAmLh9cJm2qzs2YHs2wfEW0Pk,2377
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=fhAg-FhsWpvi91BZ29EztKO3QfANRW-hnfzIkq45q3Y,2983
9
+ widgetastic_patternfly5/charts/line_chart.py,sha256=9gjoUqWopSYTng0iRpvppm-CL-QA8H_HpTvFddg0sB4,3995
10
+ widgetastic_patternfly5/charts/pie_chart.py,sha256=rIxMv6vPERj9FnTp8daejL7MxUTKFCTj9057C1KuuKw,218
11
+ widgetastic_patternfly5/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ widgetastic_patternfly5/components/alert.py,sha256=8wF4348n7Yp6BnOlbDNGw4_fFKyyjzgpQDOe9a96EsI,2579
13
+ widgetastic_patternfly5/components/breadcrumb.py,sha256=_lQ7pqmh2P9FSwMae_quUTteHsfXFlQvYaXE4Z1gK1g,2035
14
+ widgetastic_patternfly5/components/button.py,sha256=dduqxjnTHsHLkPO9TjbWO4HUfpiObMM4yuq3_q8XlA0,3539
15
+ widgetastic_patternfly5/components/card.py,sha256=YScmzLsxO72ITZFz9u3vyxpD2r0QAzP4yaBmJD9FcZE,2677
16
+ widgetastic_patternfly5/components/chip.py,sha256=t5_9oTJ__9ccwrCMzVYllDFY7oyJcMwwWKbr3jiIK0w,11218
17
+ widgetastic_patternfly5/components/clipboard_copy.py,sha256=r9RESokgZ7s5C2_ByOGLqfIjUx15-ZzFi5usSTdb-ac,1292
18
+ widgetastic_patternfly5/components/description_list.py,sha256=sVhKwKnvJguZh9FqyPC1NU2UA_w7uA3ugkY5eoVRlPk,989
19
+ widgetastic_patternfly5/components/drawer.py,sha256=kr2q042x4ghxu6T_C13mlRZZ-gZavGsVvM9a1GVYZZA,1084
20
+ widgetastic_patternfly5/components/dual_list_selector.py,sha256=jL1SOfnR5z_rWniN-KU9a26n-0HxBcL2pJbBcOFT2b8,4839
21
+ widgetastic_patternfly5/components/expandable_section.py,sha256=QCqonn1kiYfL-mGcjwrG6x4TiJBr9RKXZvyDerVTUmQ,1876
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
25
+ widgetastic_patternfly5/components/popover.py,sha256=_XZlM4v8gsmen73DoSpylMXAMeEkdkjRsuBIIKHHhtE,2242
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
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=zGIgIsYEWH3VPVNBVIPVaKlnRY61648tM8rGtohmy7s,3307
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
35
+ widgetastic_patternfly5/components/forms/radio.py,sha256=d1fpzoicgSDgoV8K9bHlXFvHEjH3zw59t_WbxTGZyQ8,2009
36
+ widgetastic_patternfly5/components/menus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ widgetastic_patternfly5/components/menus/context_selector.py,sha256=Bqj8MrxJypLfjcqrrKQj9jfFWHeSWbHIVcH2aA6sRQ8,1156
38
+ widgetastic_patternfly5/components/menus/dropdown.py,sha256=NUPpKIV-mBzmFVRPjWu0FiCWc9DjcRErK_FOxfag8y0,10471
39
+ widgetastic_patternfly5/components/menus/menu.py,sha256=sRkb46XWMNGueDCkOjs4vyb9Ehjiru71DY3MD2qsorM,8138
40
+ widgetastic_patternfly5/components/menus/menu_toggle.py,sha256=PBwNvg6E2RPdCG6ALBUqyTZxCwThJgk5S-9ghTiZ9hM,1029
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.2.17.1.dist-info/METADATA,sha256=u3xxVUmW53slGMz9XD6BL7XP-TIGG1sH5Khopll1Vw8,6826
44
+ widgetastic.patternfly5-25.2.17.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ widgetastic.patternfly5-25.2.17.1.dist-info/licenses/LICENSE,sha256=nDhhj8jp0XsTdmvWpTWFpOKVn0LPXPb6ecA9zFF3Exk,576
46
+ widgetastic.patternfly5-25.2.17.1.dist-info/RECORD,,
@@ -1,75 +1,64 @@
1
1
  from .charts.boxplot_chart import BoxPlotChart
2
2
  from .charts.bullet_chart import BulletChart
3
3
  from .charts.donut_chart import DonutChart
4
- from .charts.legend import DataPoint
5
- from .charts.legend import Legend
4
+ from .charts.legend import DataPoint, Legend
6
5
  from .charts.line_chart import LineChart
7
6
  from .charts.pie_chart import PieChart
8
7
  from .components.alert import Alert
9
8
  from .components.breadcrumb import BreadCrumb
10
9
  from .components.button import Button
11
- from .components.card import Card
12
- from .components.card import CardCheckBox
13
- from .components.card import CardForCardGroup
14
- from .components.card import CardGroup
15
- from .components.card import CardWithActions
16
- from .components.chip import CategoryChipGroup
17
- from .components.chip import Chip
18
- from .components.chip import ChipGroup
19
- from .components.chip import ChipGroupToolbar
20
- from .components.chip import ChipGroupToolbarCategory
21
- from .components.chip import ChipReadOnlyError
22
- from .components.chip import StandAloneChipGroup
10
+ from .components.card import Card, CardCheckBox, CardForCardGroup, CardGroup, CardWithActions
11
+ from .components.chip import (
12
+ CategoryChipGroup,
13
+ Chip,
14
+ ChipGroup,
15
+ ChipGroupToolbar,
16
+ ChipGroupToolbarCategory,
17
+ ChipReadOnlyError,
18
+ StandAloneChipGroup,
19
+ )
23
20
  from .components.clipboard_copy import ClipboardCopy
24
21
  from .components.date_and_time.calendar_month import CalendarMonth
25
22
  from .components.description_list import DescriptionList
26
23
  from .components.drawer import Drawer
27
- from .components.dual_list_selector import DualListSelector
28
- from .components.dual_list_selector import SearchDualListSelector
24
+ from .components.dual_list_selector import DualListSelector, SearchDualListSelector
29
25
  from .components.expandable_section import ExpandableSection
30
- from .components.forms.form_select import FormSelect
31
- from .components.forms.form_select import FormSelectDisabled
32
- from .components.forms.form_select import FormSelectOptionDisabled
33
- from .components.forms.form_select import FormSelectOptionNotFound
26
+ from .components.forms.form_select import (
27
+ FormSelect,
28
+ FormSelectDisabled,
29
+ FormSelectOptionDisabled,
30
+ FormSelectOptionNotFound,
31
+ )
34
32
  from .components.forms.radio import Radio
35
33
  from .components.menus.context_selector import ContextSelector
36
- from .components.menus.dropdown import Dropdown
37
- from .components.menus.dropdown import DropdownDisabled
38
- from .components.menus.dropdown import DropdownItemDisabled
39
- from .components.menus.dropdown import DropdownItemNotFound
40
- from .components.menus.dropdown import GroupDropdown
41
- from .components.menus.menu import CheckboxMenu
42
- from .components.menus.menu import Menu
43
- from .components.menus.menu import MenuItemDisabled
44
- from .components.menus.menu import MenuItemNotFound
34
+ from .components.menus.dropdown import (
35
+ Dropdown,
36
+ DropdownDisabled,
37
+ DropdownItemDisabled,
38
+ DropdownItemNotFound,
39
+ GroupDropdown,
40
+ )
41
+ from .components.menus.menu import CheckboxMenu, Menu, MenuItemDisabled, MenuItemNotFound
45
42
  from .components.menus.menu_toggle import SplitButtonDropdown
46
43
  from .components.menus.options_menu import OptionsMenu
47
- from .components.menus.select import CheckboxSelect
48
- from .components.menus.select import Select
49
- from .components.menus.select import SelectItemDisabled
50
- from .components.menus.select import SelectItemNotFound
51
- from .components.modal import Modal
52
- from .components.modal import ModalItemNotFound
53
- from .components.navigation import Navigation
54
- from .components.navigation import NavSelectionNotFound
55
- from .components.pagination import CompactPagination
56
- from .components.pagination import Pagination
57
- from .components.pagination import PaginationNavDisabled
44
+ from .components.menus.select import CheckboxSelect, Select, SelectItemDisabled, SelectItemNotFound
45
+ from .components.modal import Modal, ModalItemNotFound
46
+ from .components.navigation import Navigation, NavSelectionNotFound
47
+ from .components.pagination import CompactPagination, Pagination, PaginationNavDisabled
58
48
  from .components.popover import Popover
59
49
  from .components.progress import Progress
60
- from .components.slider import InputSlider
61
- from .components.slider import Slider
62
- from .components.switch import Switch
63
- from .components.switch import SwitchDisabled
64
- from .components.table import ColumnNotExpandable
65
- from .components.table import CompoundExpandableTable
66
- from .components.table import ExpandableTable
67
- from .components.table import PatternflyTable
68
- from .components.table import RowNotExpandable
50
+ from .components.slider import InputSlider, Slider
51
+ from .components.switch import Switch, SwitchDisabled
52
+ from .components.table import (
53
+ ColumnNotExpandable,
54
+ CompoundExpandableTable,
55
+ ExpandableTable,
56
+ PatternflyTable,
57
+ RowNotExpandable,
58
+ )
69
59
  from .components.tabs import Tab
70
60
  from .components.title import Title
71
61
 
72
-
73
62
  __all__ = [
74
63
  "Alert",
75
64
  "BreadCrumb",
@@ -1,12 +1,10 @@
1
1
  import re
2
2
 
3
3
  from widgetastic.utils import ParametrizedLocator
4
- from widgetastic.widget import Text
5
- from widgetastic.widget import View
4
+ from widgetastic.widget import Text, View
6
5
  from widgetastic.xpath import quote
7
6
 
8
- from .legend import DataPoint
9
- from .legend import Legend
7
+ from .legend import DataPoint, Legend
10
8
 
11
9
 
12
10
  class BulletChart(View):
@@ -36,7 +34,7 @@ class BulletChart(View):
36
34
  def __init__(self, parent=None, id=None, locator=None, logger=None, *args, **kwargs):
37
35
  View.__init__(self, parent=parent, logger=logger)
38
36
  if id:
39
- self.locator = ".//div[@id={}]".format(quote(id))
37
+ self.locator = f".//div[@id={quote(id)}]"
40
38
  elif locator:
41
39
  self.locator = locator
42
40
  else:
@@ -1,17 +1,13 @@
1
1
  import re
2
2
 
3
- from widgetastic.widget import ClickableMixin
4
- from widgetastic.widget import ParametrizedLocator
5
- from widgetastic.widget import ParametrizedView
6
- from widgetastic.widget import View
7
- from widgetastic.widget import Widget
3
+ from widgetastic.widget import ClickableMixin, ParametrizedLocator, ParametrizedView, View, Widget
8
4
  from widgetastic.xpath import quote
9
5
 
10
6
 
11
7
  class DonutLegendItem(ParametrizedView, ClickableMixin):
12
8
  PARAMETERS = ("label_text",)
13
9
  ROOT = ParametrizedLocator(
14
- ".//*[name()='text']" "/*[name()='tspan' and contains(., '{label_text}')]"
10
+ ".//*[name()='text']/*[name()='tspan' and contains(., '{label_text}')]"
15
11
  )
16
12
  ALL_ITEMS = ".//*[name()='text']/*[name()='tspan']"
17
13
  LEGEND_ITEM_REGEX = re.compile(r"(.*?): ([\d]+)")
@@ -1,5 +1,4 @@
1
- from widgetastic.widget import ParametrizedLocator
2
- from widgetastic.widget import View
1
+ from widgetastic.widget import ParametrizedLocator, View
3
2
  from widgetastic.xpath import quote
4
3
 
5
4
  from .legend import Legend
@@ -37,7 +36,7 @@ class LineChart(View):
37
36
  assert id or locator, "Provide id or locator."
38
37
 
39
38
  if id:
40
- self.locator = ".//div[@id={}]".format(quote(id))
39
+ self.locator = f".//div[@id={quote(id)}]"
41
40
  else:
42
41
  self.locator = locator
43
42
 
@@ -96,7 +95,17 @@ class LineChart(View):
96
95
  self.browser.elements(self.TOOLTIP_LABLES, parent=tooltip_el),
97
96
  self.browser.elements(self.TOOLTIP_VALUES, parent=tooltip_el),
98
97
  ):
99
- label_data[self.browser.text(label_el)] = self.browser.text(value_el)
98
+ # Get the label text and handle empty labels
99
+ label_txt = self.browser.text(label_el)
100
+ if label_txt == "":
101
+ label_txt = "Unknown"
102
+
103
+ # Update label_data with the value
104
+ value_txt = self.browser.text(value_el)
105
+ if label_txt in label_data:
106
+ label_data[label_txt] = f"{label_data[label_txt]}, {value_txt}"
107
+ else:
108
+ label_data[label_txt] = value_txt
100
109
 
101
110
  _data[x_axis_label] = label_data
102
111
 
@@ -57,12 +57,11 @@ class BaseAlert:
57
57
  for class_ in self.browser.classes(self):
58
58
  if class_ in self.TYPE_MAPPING:
59
59
  return self.TYPE_MAPPING[class_]
60
- else:
61
- raise ValueError(
62
- "Could not find a proper alert type."
63
- f"\nAvailable classes: {self.TYPE_MAPPING!r} "
64
- f"\nAlert has: {self.browser.classes(self)!r}"
65
- )
60
+ raise ValueError(
61
+ "Could not find a proper alert type."
62
+ f"\nAvailable classes: {self.TYPE_MAPPING!r} "
63
+ f"\nAlert has: {self.browser.classes(self)!r}"
64
+ )
66
65
 
67
66
  def assert_no_error(self):
68
67
  """Asserts that the warning is not of the error type."""
@@ -1,6 +1,5 @@
1
1
  from widgetastic.utils import ParametrizedLocator
2
- from widgetastic.widget import ClickableMixin
3
- from widgetastic.widget import Widget
2
+ from widgetastic.widget import ClickableMixin, Widget
4
3
  from widgetastic.xpath import quote
5
4
 
6
5
 
@@ -36,7 +35,7 @@ class BaseButton:
36
35
  return check1 or check2 or self.browser.get_attribute("disabled", self) is not None
37
36
 
38
37
  def __repr__(self):
39
- return "{}{}".format(type(self).__name__, self.locator)
38
+ return f"{type(self).__name__}{self.locator}"
40
39
 
41
40
  @property
42
41
  def title(self):
@@ -69,25 +68,25 @@ class Button(BaseButton, Widget, ClickableMixin):
69
68
  if kwargs: # classes should have been the only kwarg combined with text args
70
69
  raise TypeError("If you pass button text then only pass classes in addition")
71
70
  if len(text) == 1:
72
- locator_conditions = "normalize-space(.)={}".format(quote(text[0]))
71
+ locator_conditions = f"normalize-space(.)={quote(text[0])}"
73
72
  elif len(text) == 2 and text[0].lower() == "contains":
74
- locator_conditions = "contains(normalize-space(.), {})".format(quote(text[1]))
73
+ locator_conditions = f"contains(normalize-space(.), {quote(text[1])})"
75
74
  else:
76
75
  raise TypeError("An illegal combination of args/kwargs")
77
76
  else:
78
77
  # Join the kwargs, if any
79
78
  locator_conditions = " and ".join(
80
- "@{}={}".format(attr, quote(value)) for attr, value in kwargs.items()
79
+ f"@{attr}={quote(value)}" for attr, value in kwargs.items()
81
80
  )
82
81
 
83
82
  if classes:
84
83
  if locator_conditions:
85
84
  locator_conditions += " and "
86
85
  locator_conditions += " and ".join(
87
- "contains(@class, {})".format(quote(klass)) for klass in classes
86
+ f"contains(@class, {quote(klass)})" for klass in classes
88
87
  )
89
88
  if locator_conditions:
90
- locator_conditions = "and ({})".format(locator_conditions)
89
+ locator_conditions = f"and ({locator_conditions})"
91
90
 
92
91
  return (
93
92
  ".//*[(self::a or self::button or (self::input and "
@@ -1,8 +1,5 @@
1
1
  from widgetastic.utils import ParametrizedLocator
2
- from widgetastic.widget import Checkbox
3
- from widgetastic.widget import GenericLocatorWidget
4
- from widgetastic.widget import ParametrizedView
5
- from widgetastic.widget import View
2
+ from widgetastic.widget import Checkbox, GenericLocatorWidget, ParametrizedView, View
6
3
 
7
4
  from widgetastic_patternfly5.components.menus.dropdown import Dropdown
8
5
 
@@ -34,9 +31,7 @@ class BaseCard:
34
31
 
35
32
 
36
33
  class Card(BaseCard, GenericLocatorWidget):
37
- DEFAULT_LOCATOR = (
38
- ".//div[@data-ouia-component-type='PF5/Card'] | .//article[contains(@class, '-c-card')]"
39
- )
34
+ DEFAULT_LOCATOR = ".//div[contains(@data-ouia-component-type, '/Card')] | .//article[contains(@class, '-c-card')]"
40
35
 
41
36
  def __init__(self, parent, locator=None, logger=None):
42
37
  locator = locator or self.DEFAULT_LOCATOR
@@ -46,9 +41,7 @@ class Card(BaseCard, GenericLocatorWidget):
46
41
 
47
42
 
48
43
  class CardForCardGroup(BaseCard, ParametrizedView):
49
- DEFAULT_LOCATOR = (
50
- "(.//div[@data-ouia-component-type='PF5/Card'] | .//article[contains(@class, '-c-card')])"
51
- )
44
+ DEFAULT_LOCATOR = "(.//div[contains(@data-ouia-component-type, '/Card')] | .//article[contains(@class, '-c-card')])"
52
45
 
53
46
  def __init__(self, parent, locator=None, logger=None, **kwargs):
54
47
  View.__init__(self, parent, logger=logger, **kwargs)
@@ -1,9 +1,6 @@
1
1
  # TODO: This imported directly form wt.pfy4. I thnk we need to restructure it.
2
2
  from wait_for import wait_for
3
- from widgetastic.widget import ParametrizedLocator
4
- from widgetastic.widget import ParametrizedView
5
- from widgetastic.widget import Text
6
- from widgetastic.widget import View
3
+ from widgetastic.widget import ParametrizedLocator, ParametrizedView, Text, View
7
4
 
8
5
  from .button import Button
9
6
 
@@ -15,18 +12,18 @@ class ChipReadOnlyError(Exception):
15
12
 
16
13
 
17
14
  CHIP_ROOT = (
18
- ".//div[contains(@class, '-c-chip') and not(contains(@class, '-m-overflow')) "
19
- "and not(contains(@class, '-c-chip-group'))]"
15
+ ".//*[contains(@data-ouia-component-type, '/Chip') and not(contains(@class, '-m-overflow')) "
16
+ "and not(contains(@class, '-c-chip-group') or contains(@class, '-c-label-group'))]"
20
17
  )
21
- CHIP_TEXT = ".//span[contains(@class, '-c-chip__text')]"
18
+ CHIP_TEXT = ".//span[contains(@class, '-c-chip__text') or contains(@class, '-c-label__text')]"
22
19
  CHIP_BADGE = ".//span[contains(@class, '-c-badge')]"
23
- GROUP_ROOT = ".//div[contains(@class, '-c-chip-group') and @role='group']"
20
+ GROUP_ROOT = ".//div[(contains(@class, '-c-chip-group') and @role='group') or contains(@class, '-c-label-group')]"
24
21
  CATEGORY_GROUP_ROOT = (
25
- ".//div[contains(@class, '-c-chip-group') and @role='group' "
22
+ ".//div[((contains(@class, '-c-chip-group') and @role='group') or contains(@class, '-c-label-group')) "
26
23
  "and contains(@class, 'pf-m-category')]"
27
24
  )
28
- CATEGORY_LABEL = ".//span[contains(@class, '-c-chip-group__label')]"
29
- CATEGORY_CLOSE = ".//div[contains(@class, '-c-chip-group__close')]/button"
25
+ CATEGORY_LABEL = ".//span[contains(@class, '-group__label')]"
26
+ CATEGORY_CLOSE = ".//div[contains(@class, '-group__close')]/button"
30
27
  # For backwards compatibility
31
28
  OLD_GROUP_ROOT = ".//ul[contains(@class, '-c-chip-group')]"
32
29
  TOOLBAR_GROUP_LABEL = "./li/*[contains(@class, '-c-chip-group__label')]"
@@ -44,7 +41,7 @@ class _BaseChip(View):
44
41
 
45
42
  _text = Text(CHIP_TEXT)
46
43
  _badge = Text(f"{CHIP_TEXT}/{CHIP_BADGE}")
47
- button = Button(**{"aria-label": "close"})
44
+ button = Button()
48
45
 
49
46
  @property
50
47
  def badge(self):
@@ -122,7 +119,7 @@ class OverflowChip(_BaseChip):
122
119
  The 'Show More'/'Show Less' button is essentially a special kind of chip
123
120
  """
124
121
 
125
- ROOT = ".//button[contains(@class, '-c-chip') and contains(@class, 'pf-m-overflow')]"
122
+ ROOT = ".//button[(contains(@class, '-c-chip') or contains(@class, '-c-label')) and contains(@class, 'pf-m-overflow')]"
126
123
 
127
124
  def _show_less_shown(self):
128
125
  return self.text.replace(" ", "").lower() == "showless"
@@ -327,7 +324,7 @@ class ChipGroupToolbar(View):
327
324
  # The parent of the chip group toolbar can be any element type
328
325
  # The locator should be the parent node which holds all the pf-c-chip-group elements
329
326
  TOOLBAR_LOCATOR = (
330
- ".//ul[contains(@class, '-c-chip-group') and " "contains(@class, 'pf-m-toolbar')]/parent::*"
327
+ ".//ul[contains(@class, '-c-chip-group') and contains(@class, 'pf-m-toolbar')]/parent::*"
331
328
  )
332
329
 
333
330
  overflow = OldOverflowChip(
@@ -1,7 +1,5 @@
1
1
  from widgetastic.utils import ParametrizedLocator
2
- from widgetastic.widget import GenericLocatorWidget
3
- from widgetastic.widget import Text
4
- from widgetastic.widget import TextInput
2
+ from widgetastic.widget import GenericLocatorWidget, Text, TextInput
5
3
 
6
4
  from widgetastic_patternfly5 import Button
7
5
 
@@ -1,14 +1,17 @@
1
+ from wait_for import wait_for
1
2
  from widgetastic.utils import ParametrizedLocator
2
3
  from widgetastic.widget import View
3
4
 
5
+ from widgetastic_patternfly5 import Button
6
+
4
7
 
5
8
  class BaseDrawer:
6
- """Represents drawer component for pf5
9
+ """Represents drawer component for pf5/pf6
7
10
 
8
11
  https://www.patternfly.org/components/drawer
9
12
  """
10
13
 
11
- CLOSE = ".//button[@aria-label='Close drawer panel']"
14
+ close_btn = Button(locator=".//div[contains(@class, '-c-drawer__close')]/button")
12
15
 
13
16
  @property
14
17
  def is_open(self):
@@ -18,7 +21,8 @@ class BaseDrawer:
18
21
  def close(self):
19
22
  """Close drawer."""
20
23
  if self.is_open:
21
- self.browser.click(self.browser.element(self.CLOSE))
24
+ self.close_btn.click()
25
+ wait_for(lambda: not self.is_open, num_sec=10)
22
26
 
23
27
 
24
28
  class Drawer(BaseDrawer, View):
@@ -1,5 +1,4 @@
1
- from widgetastic.widget import GenericLocatorWidget
2
- from widgetastic.widget import TextInput
1
+ from widgetastic.widget import GenericLocatorWidget, TextInput
3
2
 
4
3
  from .button import Button
5
4
 
@@ -81,18 +81,14 @@ class BaseFormSelect:
81
81
  FormSelectOptionNotFound: if option not found
82
82
  """
83
83
  if not self.is_enabled:
84
- raise FormSelectDisabled("{} is not enabled".format(repr(self)))
84
+ raise FormSelectDisabled(f"{repr(self)} is not enabled")
85
85
  if value not in self.all_options:
86
86
  raise FormSelectOptionNotFound(
87
- 'Option "{}" not found in {}. Available options: {}'.format(
88
- value, repr(self), self.all_options
89
- )
87
+ f'Option "{value}" not found in {repr(self)}. Available options: {self.all_options}'
90
88
  )
91
89
  elif value not in self.all_enabled_options:
92
90
  raise FormSelectOptionDisabled(
93
- 'Option "{}" is disabled in {}. Enabled options are: {}'.format(
94
- value, repr(self), self.all_enabled_options
95
- )
91
+ f'Option "{value}" is disabled in {repr(self)}. Enabled options are: {self.all_enabled_options}'
96
92
  )
97
93
  self._select_element.select_by_visible_text(value)
98
94
 
@@ -101,7 +97,7 @@ class BaseFormSelect:
101
97
  return self.browser.text(self._select_element.first_selected_option)
102
98
 
103
99
  def __repr__(self):
104
- return "{}({!r})".format(type(self).__name__, self.locator)
100
+ return f"{type(self).__name__}({self.locator!r})"
105
101
 
106
102
 
107
103
  class FormSelect(BaseFormSelect, GenericLocatorWidget):
@@ -1,7 +1,4 @@
1
- from widgetastic.widget import Checkbox
2
- from widgetastic.widget import ParametrizedLocator
3
- from widgetastic.widget import Text
4
- from widgetastic.widget import View
1
+ from widgetastic.widget import Checkbox, ParametrizedLocator, Text, View
5
2
 
6
3
 
7
4
  class BaseRadio:
@@ -3,10 +3,10 @@ from .select import Select
3
3
 
4
4
  class BaseContextSelector:
5
5
  ITEMS_LOCATOR = (
6
- ".//ul[@class='pf-v5-c-menu__list' or @class='pf-c-context-selector__menu-list']/li"
6
+ ".//ul[contains(@class, '-c-menu__list') or @class='pf-c-context-selector__menu-list']/li"
7
7
  )
8
8
  ITEM_LOCATOR = (
9
- ".//*[(contains(@class, 'pf-v5-c-menu__item') or contains(@class, "
9
+ ".//*[(contains(@class, '-c-menu__list-item') or contains(@class, "
10
10
  "'pf-c-context-selector__menu-list-item'))"
11
11
  " and normalize-space(.)={}]"
12
12
  )
@@ -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):
@@ -85,8 +81,7 @@ class BaseCheckboxSelect(BaseSelect):
85
81
  ".//*[contains(@class, '-c-menu__list-item') or contains(@class, '-c-select__menu-item')]"
86
82
  )
87
83
  ITEM_LOCATOR = (
88
- f"{ITEMS_LOCATOR}[.//span[starts-with(normalize-space(.), {{}})]]"
89
- f"//input[@type='checkbox']"
84
+ f"{ITEMS_LOCATOR}[.//span[starts-with(normalize-space(.), {{}})]]//input[@type='checkbox']"
90
85
  )
91
86
 
92
87
  def item_select(self, items, close=True):
@@ -96,7 +91,7 @@ class BaseCheckboxSelect(BaseSelect):
96
91
  item: Item to be selected
97
92
  close: Close the dropdown when finished
98
93
  """
99
- if not isinstance(items, (list, tuple, set)):
94
+ if not isinstance(items, list | tuple | set):
100
95
  items = [items]
101
96
 
102
97
  try:
@@ -115,7 +110,7 @@ class BaseCheckboxSelect(BaseSelect):
115
110
  item: Item to be selected
116
111
  close: Close the dropdown when finished
117
112
  """
118
- if not isinstance(items, (list, tuple, set)):
113
+ if not isinstance(items, list | tuple | set):
119
114
  items = [items]
120
115
 
121
116
  try:
@@ -153,7 +148,10 @@ class BaseCheckboxSelect(BaseSelect):
153
148
  """Returns a dictionary containing the selected status as bools."""
154
149
  selected = {}
155
150
  with self.opened():
156
- 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:
157
155
  item = self.browser.text(el)
158
156
  try:
159
157
  # get the child element of the label
@@ -172,7 +170,10 @@ class BaseCheckboxSelect(BaseSelect):
172
170
  close: Close the dropdown when finished
173
171
  """
174
172
  self.open()
175
- 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]
176
177
 
177
178
  if close:
178
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
 
@@ -1,46 +0,0 @@
1
- widgetastic_patternfly5/__init__.py,sha256=YeVCoYFKRjU7Jgm8iYlikzG_aUfdPr--ayP4i_KrKuY,4859
2
- widgetastic_patternfly5/ouia.py,sha256=DCVMYscDvvhDbYlyfEnuaS1yUAqQ8Ty3sV50tZkXQHI,4299
3
- widgetastic_patternfly5/charts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- widgetastic_patternfly5/charts/alerts_timeline_chart.py,sha256=U7hJvxAG1Wgj9WFt5QxAmLh9cJm2qzs2YHs2wfEW0Pk,2377
5
- widgetastic_patternfly5/charts/boxplot_chart.py,sha256=6Ek6gwbmwNeazy-5o2fVfvkjpd6bhbTyitiPomvLBts,405
6
- widgetastic_patternfly5/charts/bullet_chart.py,sha256=G7-U1l9d8VlkiPKc3IfFtcleHAmM5WXsfAKwiyw5DEg,3737
7
- widgetastic_patternfly5/charts/donut_chart.py,sha256=phPoVkvxdfUqxsHB8uWvdFUhWGT3ECi89J8H7VCySx4,2693
8
- widgetastic_patternfly5/charts/legend.py,sha256=fhAg-FhsWpvi91BZ29EztKO3QfANRW-hnfzIkq45q3Y,2983
9
- widgetastic_patternfly5/charts/line_chart.py,sha256=-TetZ4LiF35p4P1KYc_DE5vpvd4tevytaQx0at2TRP0,3612
10
- widgetastic_patternfly5/charts/pie_chart.py,sha256=rIxMv6vPERj9FnTp8daejL7MxUTKFCTj9057C1KuuKw,218
11
- widgetastic_patternfly5/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- widgetastic_patternfly5/components/alert.py,sha256=lSh2TxSOxLob4VO0J7fr-W09R2_Sa4RLRtR5XBjA4_U,2613
13
- widgetastic_patternfly5/components/breadcrumb.py,sha256=_lQ7pqmh2P9FSwMae_quUTteHsfXFlQvYaXE4Z1gK1g,2035
14
- widgetastic_patternfly5/components/button.py,sha256=taiZYjFez5zVyOUBklIIz-kEGFLjigf0I97G2PYepCI,3621
15
- widgetastic_patternfly5/components/card.py,sha256=wwk9QqXl66ikvZ0Q40Sir0JWI2AHxtYSNCRZOJB1YfA,2783
16
- widgetastic_patternfly5/components/chip.py,sha256=CidCOMOPBVYG8ubg2DHLX6aj9NJhAv1RVWEfQKNdL4A,11142
17
- widgetastic_patternfly5/components/clipboard_copy.py,sha256=klEgT2RN4Sc7pd7Erwlxyj6ahOwexeMkTzyn6cCHNRk,1352
18
- widgetastic_patternfly5/components/description_list.py,sha256=sVhKwKnvJguZh9FqyPC1NU2UA_w7uA3ugkY5eoVRlPk,989
19
- widgetastic_patternfly5/components/drawer.py,sha256=NuWK3DLpY6YmGs3ANi5XGizJRYhEnrrLcucP2ZFZHoA,949
20
- widgetastic_patternfly5/components/dual_list_selector.py,sha256=tPT3cMmKVU3vs-iEW0D-iH-E17MOufWyrgQQUm5kHzY,4869
21
- widgetastic_patternfly5/components/expandable_section.py,sha256=QCqonn1kiYfL-mGcjwrG6x4TiJBr9RKXZvyDerVTUmQ,1876
22
- widgetastic_patternfly5/components/modal.py,sha256=842x6D9Xe4LRW0YA8W-rGp8Ak8tHwinD_zktLCYsjtg,2003
23
- widgetastic_patternfly5/components/navigation.py,sha256=9CdnD23hlBtfJAi1RDFRSbTpHc7Rz8wS1W-C9XWOGZw,5361
24
- widgetastic_patternfly5/components/pagination.py,sha256=YIDYCEATEp4BVy0JZYtNIRGy-dKbgZP2WOzZ6z3c56s,8843
25
- widgetastic_patternfly5/components/popover.py,sha256=_XZlM4v8gsmen73DoSpylMXAMeEkdkjRsuBIIKHHhtE,2242
26
- widgetastic_patternfly5/components/progress.py,sha256=VWgg48c5YtnaCO4DJopks0ZXJsmpzpD-OUOUaMoCC94,1413
27
- widgetastic_patternfly5/components/slider.py,sha256=B9gr8mnndLWivSlS9lIRd3SW6W5Iev9ESnQE9b0A55Y,3128
28
- widgetastic_patternfly5/components/switch.py,sha256=7BkpxRb7aKVWwQn1r_80zvcHlu8H_-sOoKVf1ebgHbY,2090
29
- widgetastic_patternfly5/components/table.py,sha256=wuA_L2wu2AeTyepeHoMzBoYVAl1REr0lNq7p08BfCRM,16989
30
- widgetastic_patternfly5/components/tabs.py,sha256=ywk8zfuQ8m2IS7gd84xWQOjMwQjtJS1iCNIg9vDrwxA,2097
31
- widgetastic_patternfly5/components/title.py,sha256=ZSAT4baAOTBYsM4d9Jt74C7JR6yhRE7ewzMd3Ez6QGk,950
32
- widgetastic_patternfly5/components/date_and_time/calendar_month.py,sha256=zGIgIsYEWH3VPVNBVIPVaKlnRY61648tM8rGtohmy7s,3307
33
- widgetastic_patternfly5/components/forms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- widgetastic_patternfly5/components/forms/form_select.py,sha256=jQUpwJnJnOl5_0XzeplpHx9vz-6GMXUETny-F1TAU-o,3649
35
- widgetastic_patternfly5/components/forms/radio.py,sha256=9I7P9hsKRojGvTZtSsqVTI_4ek_qIYKEdamSdUfxsMA,2099
36
- widgetastic_patternfly5/components/menus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- widgetastic_patternfly5/components/menus/context_selector.py,sha256=uMumIPxuY3f_r5o9JrMibiPQgpFjRaFQwwo08_rVeMQ,1150
38
- widgetastic_patternfly5/components/menus/dropdown.py,sha256=5nCz0HRG4VcrzD1NFC9Hj1m2T4yHihfNf2MNQWnBOEk,9478
39
- widgetastic_patternfly5/components/menus/menu.py,sha256=0cUb5V1y3RgRpdAB8DlPIL9OOl7IxBC8A1xjcZgfLBA,7949
40
- widgetastic_patternfly5/components/menus/menu_toggle.py,sha256=PBwNvg6E2RPdCG6ALBUqyTZxCwThJgk5S-9ghTiZ9hM,1029
41
- widgetastic_patternfly5/components/menus/options_menu.py,sha256=43D0_JHeY-kPORQi54NL9OVmcMyAolVnI4dqHpo5omY,1353
42
- widgetastic_patternfly5/components/menus/select.py,sha256=7iT6X9RaXrZWNbMaMZ2tJfCJKKJyaNUjTCktpyH8YKQ,6354
43
- widgetastic.patternfly5-25.2.11.0.dist-info/METADATA,sha256=RdkbNf1RFcCM6EMqlHDAF2haQhMQ7HYMJ8Gnfxlo5m0,7078
44
- widgetastic.patternfly5-25.2.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
- widgetastic.patternfly5-25.2.11.0.dist-info/licenses/LICENSE,sha256=nDhhj8jp0XsTdmvWpTWFpOKVn0LPXPb6ecA9zFF3Exk,576
46
- widgetastic.patternfly5-25.2.11.0.dist-info/RECORD,,