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