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.
- {widgetastic.patternfly5-25.2.10.0.dist-info → widgetastic.patternfly5-25.2.17.1.dist-info}/METADATA +9 -12
- widgetastic.patternfly5-25.2.17.1.dist-info/RECORD +46 -0
- widgetastic_patternfly5/__init__.py +39 -50
- widgetastic_patternfly5/charts/alerts_timeline_chart.py +65 -0
- widgetastic_patternfly5/charts/bullet_chart.py +3 -5
- widgetastic_patternfly5/charts/donut_chart.py +2 -6
- widgetastic_patternfly5/charts/line_chart.py +13 -4
- widgetastic_patternfly5/components/alert.py +5 -6
- widgetastic_patternfly5/components/button.py +7 -8
- widgetastic_patternfly5/components/card.py +3 -10
- widgetastic_patternfly5/components/chip.py +11 -14
- widgetastic_patternfly5/components/clipboard_copy.py +1 -3
- widgetastic_patternfly5/components/drawer.py +7 -3
- widgetastic_patternfly5/components/dual_list_selector.py +1 -2
- widgetastic_patternfly5/components/forms/form_select.py +4 -8
- widgetastic_patternfly5/components/forms/radio.py +1 -4
- widgetastic_patternfly5/components/menus/context_selector.py +2 -2
- widgetastic_patternfly5/components/menus/dropdown.py +38 -13
- widgetastic_patternfly5/components/menus/menu.py +19 -18
- widgetastic_patternfly5/components/menus/options_menu.py +4 -3
- widgetastic_patternfly5/components/menus/select.py +14 -18
- widgetastic_patternfly5/components/modal.py +1 -1
- widgetastic_patternfly5/components/navigation.py +3 -3
- widgetastic_patternfly5/components/pagination.py +3 -8
- widgetastic_patternfly5/components/progress.py +2 -3
- widgetastic_patternfly5/components/switch.py +8 -7
- widgetastic_patternfly5/components/table.py +21 -16
- widgetastic_patternfly5/components/tabs.py +7 -4
- widgetastic_patternfly5/components/title.py +1 -2
- widgetastic_patternfly5/ouia.py +5 -10
- widgetastic.patternfly5-25.2.10.0.dist-info/RECORD +0 -45
- {widgetastic.patternfly5-25.2.10.0.dist-info → widgetastic.patternfly5-25.2.17.1.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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 =
|
|
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. {}"
|
|
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
|
-
|
|
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
|
-
{
|
|
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: {
|
|
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
|
-
|
|
52
|
-
self.
|
|
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
|
|
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: {}"
|
|
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: {}"
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
self.
|
|
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: {}"
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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')]
|
|
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 "{
|
|
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={}"
|
|
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={
|
|
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"
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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"
|
|
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"
|
|
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.
|
|
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.
|
|
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 "{
|
|
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)[{
|
|
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
|
|
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 "{
|
|
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[{
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
235
|
-
|
|
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[{
|
|
337
|
+
return f"./tr[1]/th[{self.position + 1}]"
|
|
333
338
|
else:
|
|
334
|
-
return "./tr[1]/td[{}]"
|
|
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
|
|
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
|
-
|
|
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=
|
|
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}>"
|
|
67
|
+
return f"<Tab {self.tab_name!r}>"
|
widgetastic_patternfly5/ouia.py
CHANGED
|
@@ -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.
|
|
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
|
|