robotframework-okw-web-selenium 0.3.0__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.
- okw_web_selenium/__init__.py +3 -0
- okw_web_selenium/adapters/__init__.py +0 -0
- okw_web_selenium/adapters/selenium_web.py +209 -0
- okw_web_selenium/keywords/__init__.py +0 -0
- okw_web_selenium/keywords/web_keywords.py +28 -0
- okw_web_selenium/library.py +55 -0
- okw_web_selenium/locators/Chrome.yaml +14 -0
- okw_web_selenium/locators/Firefox.yaml +14 -0
- okw_web_selenium/locators/__init__.py +0 -0
- okw_web_selenium/widgets/__init__.py +43 -0
- okw_web_selenium/widgets/host/__init__.py +0 -0
- okw_web_selenium/widgets/host/browsercontrol/BrowserControl.py +10 -0
- okw_web_selenium/widgets/host/browsercontrol/UrlBar.py +11 -0
- okw_web_selenium/widgets/host/browsercontrol/__init__.py +2 -0
- okw_web_selenium/widgets/webse_base.py +256 -0
- okw_web_selenium/widgets/webse_button.py +11 -0
- okw_web_selenium/widgets/webse_checkbox.py +20 -0
- okw_web_selenium/widgets/webse_combobox.py +103 -0
- okw_web_selenium/widgets/webse_label.py +10 -0
- okw_web_selenium/widgets/webse_link.py +11 -0
- okw_web_selenium/widgets/webse_listbox.py +38 -0
- okw_web_selenium/widgets/webse_multilinefield.py +11 -0
- okw_web_selenium/widgets/webse_radiolist.py +114 -0
- okw_web_selenium/widgets/webse_table.py +90 -0
- okw_web_selenium/widgets/webse_textfield.py +21 -0
- robotframework_okw_web_selenium-0.3.0.dist-info/METADATA +222 -0
- robotframework_okw_web_selenium-0.3.0.dist-info/RECORD +30 -0
- robotframework_okw_web_selenium-0.3.0.dist-info/WHEEL +5 -0
- robotframework_okw_web_selenium-0.3.0.dist-info/licenses/LICENSE +36 -0
- robotframework_okw_web_selenium-0.3.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from SeleniumLibrary import SeleniumLibrary
|
|
2
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
3
|
+
|
|
4
|
+
class SeleniumWebAdapter:
|
|
5
|
+
|
|
6
|
+
def __init__(self, browser):
|
|
7
|
+
self.browser = browser
|
|
8
|
+
self.sl = SeleniumLibrary()
|
|
9
|
+
self.sl.open_browser("about:blank", browser=self.browser)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def go_to(self, url):
|
|
13
|
+
self.sl.go_to(url)
|
|
14
|
+
|
|
15
|
+
def maximize_window(self):
|
|
16
|
+
self.sl.maximize_browser_window()
|
|
17
|
+
|
|
18
|
+
def input_text(self, locator, value):
|
|
19
|
+
resolved = self._resolve(locator)
|
|
20
|
+
self.sl.input_text(resolved, value)
|
|
21
|
+
|
|
22
|
+
def click(self, locator):
|
|
23
|
+
resolved = self._resolve(locator)
|
|
24
|
+
self.sl.click_element(resolved)
|
|
25
|
+
|
|
26
|
+
def double_click(self, locator):
|
|
27
|
+
resolved = self._resolve(locator)
|
|
28
|
+
self.sl.double_click_element(resolved)
|
|
29
|
+
|
|
30
|
+
def get_text(self, locator):
|
|
31
|
+
resolved = self._resolve(locator)
|
|
32
|
+
return self.sl.get_text(resolved)
|
|
33
|
+
|
|
34
|
+
def get_value(self, locator):
|
|
35
|
+
resolved = self._resolve(locator)
|
|
36
|
+
return self.sl.get_value(resolved)
|
|
37
|
+
|
|
38
|
+
def get_attribute(self, locator, name):
|
|
39
|
+
resolved = self._resolve(locator)
|
|
40
|
+
el = self.sl.get_webelement(resolved)
|
|
41
|
+
return el.get_attribute(name)
|
|
42
|
+
|
|
43
|
+
def clear_text(self, locator):
|
|
44
|
+
resolved = self._resolve(locator)
|
|
45
|
+
self.sl.clear_element_text(resolved)
|
|
46
|
+
|
|
47
|
+
def element_exists(self, locator):
|
|
48
|
+
try:
|
|
49
|
+
self.sl.get_webelement(self._resolve(locator))
|
|
50
|
+
return True
|
|
51
|
+
except Exception:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
def select_by_label(self, locator, label):
|
|
55
|
+
resolved = self._resolve(locator)
|
|
56
|
+
self.sl.select_from_list_by_label(resolved, label)
|
|
57
|
+
|
|
58
|
+
def select_by_value(self, locator, value):
|
|
59
|
+
resolved = self._resolve(locator)
|
|
60
|
+
self.sl.select_from_list_by_value(resolved, value)
|
|
61
|
+
|
|
62
|
+
def select_by_index(self, locator, index):
|
|
63
|
+
resolved = self._resolve(locator)
|
|
64
|
+
self.sl.select_from_list_by_index(resolved, str(index))
|
|
65
|
+
|
|
66
|
+
def press_keys(self, locator, keys):
|
|
67
|
+
target = self._resolve(locator) if locator else None
|
|
68
|
+
self.sl.press_keys(target, keys)
|
|
69
|
+
|
|
70
|
+
def wait_until_visible(self, locator, timeout=None):
|
|
71
|
+
resolved = self._resolve(locator)
|
|
72
|
+
self.sl.wait_until_element_is_visible(resolved, timeout=timeout)
|
|
73
|
+
|
|
74
|
+
def wait_until_not_visible(self, locator, timeout=None):
|
|
75
|
+
resolved = self._resolve(locator)
|
|
76
|
+
self.sl.wait_until_element_is_not_visible(resolved, timeout=timeout)
|
|
77
|
+
|
|
78
|
+
def set_checkbox(self, locator, checked: bool):
|
|
79
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
80
|
+
if bool(el.is_selected()) != bool(checked):
|
|
81
|
+
el.click()
|
|
82
|
+
|
|
83
|
+
def is_checkbox_selected(self, locator) -> bool:
|
|
84
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
85
|
+
return bool(el.is_selected())
|
|
86
|
+
|
|
87
|
+
def get_selected_list_labels(self, locator):
|
|
88
|
+
resolved = self._resolve(locator)
|
|
89
|
+
return self.sl.get_selected_list_labels(resolved)
|
|
90
|
+
|
|
91
|
+
def get_selected_list_values(self, locator):
|
|
92
|
+
resolved = self._resolve(locator)
|
|
93
|
+
return self.sl.get_selected_list_values(resolved)
|
|
94
|
+
|
|
95
|
+
def scroll_into_view(self, locator):
|
|
96
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
97
|
+
try:
|
|
98
|
+
self.sl.driver.execute_script('arguments[0].scrollIntoView({block: "center", inline: "nearest"});', el)
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
def is_enabled(self, locator) -> bool:
|
|
103
|
+
try:
|
|
104
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
105
|
+
return bool(el.is_enabled())
|
|
106
|
+
except Exception:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
def is_editable(self, locator) -> bool:
|
|
110
|
+
try:
|
|
111
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
112
|
+
if not el.is_enabled():
|
|
113
|
+
return False
|
|
114
|
+
tag = (el.tag_name or '').lower()
|
|
115
|
+
ce = (el.get_attribute('contenteditable') or '').lower()
|
|
116
|
+
if ce in ('true', 'plaintext-only'):
|
|
117
|
+
return True
|
|
118
|
+
ro = el.get_attribute('readonly')
|
|
119
|
+
if ro is not None and str(ro).lower() not in ('false', 'none'):
|
|
120
|
+
return False
|
|
121
|
+
if tag in ('input', 'textarea'):
|
|
122
|
+
return True
|
|
123
|
+
return False
|
|
124
|
+
except Exception:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def is_visible(self, locator) -> bool:
|
|
128
|
+
try:
|
|
129
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
130
|
+
return bool(el.is_displayed())
|
|
131
|
+
except Exception:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
def is_focusable(self, locator) -> bool:
|
|
135
|
+
try:
|
|
136
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
137
|
+
if not el.is_enabled():
|
|
138
|
+
return False
|
|
139
|
+
tag = (el.tag_name or '').lower()
|
|
140
|
+
ce = (el.get_attribute('contenteditable') or '').lower()
|
|
141
|
+
if ce in ('true', 'plaintext-only'):
|
|
142
|
+
return True
|
|
143
|
+
tb = el.get_attribute('tabindex')
|
|
144
|
+
if tb is not None:
|
|
145
|
+
try:
|
|
146
|
+
if int(str(tb)) >= 0:
|
|
147
|
+
return True
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
if tag in ('input', 'select', 'textarea', 'button'):
|
|
151
|
+
return True
|
|
152
|
+
if tag == 'a':
|
|
153
|
+
href = el.get_attribute('href')
|
|
154
|
+
if href:
|
|
155
|
+
return True
|
|
156
|
+
return False
|
|
157
|
+
except Exception:
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
def is_clickable(self, locator) -> bool:
|
|
161
|
+
try:
|
|
162
|
+
return bool(self.is_visible(locator) and self.is_enabled(locator))
|
|
163
|
+
except Exception:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
def unselect_all_from_list(self, locator):
|
|
167
|
+
resolved = self._resolve(locator)
|
|
168
|
+
self.sl.unselect_all_from_list(resolved)
|
|
169
|
+
|
|
170
|
+
def unselect_from_list_by_label(self, locator, labels):
|
|
171
|
+
resolved = self._resolve(locator)
|
|
172
|
+
self.sl.unselect_from_list_by_label(resolved, labels)
|
|
173
|
+
|
|
174
|
+
def unselect_from_list_by_value(self, locator, values):
|
|
175
|
+
resolved = self._resolve(locator)
|
|
176
|
+
self.sl.unselect_from_list_by_value(resolved, values)
|
|
177
|
+
|
|
178
|
+
def select_radio(self, group_name: str, value: str):
|
|
179
|
+
self.sl.select_radio_button(group_name, value)
|
|
180
|
+
|
|
181
|
+
def radio_button_should_be_set_to(self, group_name: str, value: str):
|
|
182
|
+
self.sl.radio_button_should_be_set_to(group_name, value)
|
|
183
|
+
|
|
184
|
+
def _resolve(self, locator_dict):
|
|
185
|
+
if not locator_dict:
|
|
186
|
+
raise ValueError("Locator missing")
|
|
187
|
+
if isinstance(locator_dict, str):
|
|
188
|
+
return locator_dict
|
|
189
|
+
if isinstance(locator_dict, dict):
|
|
190
|
+
if len(locator_dict) != 1:
|
|
191
|
+
raise ValueError(f"Locator must have exactly one key, got: {locator_dict}")
|
|
192
|
+
key, value = list(locator_dict.items())[0]
|
|
193
|
+
return f"{key}:{value}"
|
|
194
|
+
raise TypeError(f"Unsupported locator format: {locator_dict}")
|
|
195
|
+
|
|
196
|
+
def focus(self, locator):
|
|
197
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
198
|
+
try:
|
|
199
|
+
self.sl.driver.execute_script('arguments[0].focus();', el)
|
|
200
|
+
except Exception:
|
|
201
|
+
el.click()
|
|
202
|
+
|
|
203
|
+
def has_focus(self, locator) -> bool:
|
|
204
|
+
el = self.sl.get_webelement(self._resolve(locator))
|
|
205
|
+
try:
|
|
206
|
+
active = self.sl.driver.switch_to.active_element
|
|
207
|
+
return el == active
|
|
208
|
+
except Exception:
|
|
209
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from robot.api.deco import keyword
|
|
2
|
+
from okw4robot.runtime.context import context
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class WebKeywords:
|
|
6
|
+
"""Selenium-specific keywords for web automation."""
|
|
7
|
+
|
|
8
|
+
@keyword("ExecuteJS")
|
|
9
|
+
def execute_js(self, script: str):
|
|
10
|
+
"""Executes raw JavaScript in the current browser context.
|
|
11
|
+
|
|
12
|
+
Arguments:
|
|
13
|
+
- ``script``: JavaScript source to execute. Must be a self-contained string.
|
|
14
|
+
|
|
15
|
+
Behavior:
|
|
16
|
+
- Supported with web adapters that expose SeleniumLibrary as ``adapter.sl``.
|
|
17
|
+
Internally calls ``SeleniumLibrary.Execute Javascript`` and returns its result.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
| ${title}= | ExecuteJS | return document.title; |
|
|
21
|
+
| ${len}= | ExecuteJS | return document.querySelectorAll('input').length; |
|
|
22
|
+
| ExecuteJS | document.querySelector('#email').value = 'user@example.com'; |
|
|
23
|
+
"""
|
|
24
|
+
adapter = context.get_adapter()
|
|
25
|
+
if hasattr(adapter, 'sl') and hasattr(adapter.sl, 'execute_javascript'):
|
|
26
|
+
return adapter.sl.execute_javascript(script)
|
|
27
|
+
a = adapter.__class__.__name__
|
|
28
|
+
raise RuntimeError(f"[ExecuteJS] Not supported by adapter '{a}'")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""OKW Web Selenium - Selenium driver for OKW4Robot.
|
|
2
|
+
|
|
3
|
+
Extends OKW4RobotLibrary with Selenium-specific adapter, browser widgets,
|
|
4
|
+
and web-only keywords like ExecuteJS.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from robot.api.deco import library
|
|
9
|
+
|
|
10
|
+
from okw4robot.library import OKW4RobotLibrary
|
|
11
|
+
from .keywords.web_keywords import WebKeywords
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@library(scope="GLOBAL")
|
|
15
|
+
class OkwWebSeleniumLibrary(OKW4RobotLibrary, WebKeywords):
|
|
16
|
+
"""Selenium-based GUI test automation with OKW4Robot.
|
|
17
|
+
|
|
18
|
+
= Overview =
|
|
19
|
+
|
|
20
|
+
``OkwWebSeleniumLibrary`` extends ``OKW4RobotLibrary`` with Selenium-specific
|
|
21
|
+
capabilities: a SeleniumWebAdapter for Chrome/Firefox, browser control widgets
|
|
22
|
+
(URL bar, Maximize Window), and web-only keywords like ``ExecuteJS``.
|
|
23
|
+
|
|
24
|
+
All generic OKW4Robot keywords (``SetValue``, ``VerifyValue``, ``ClickOn``, ...)
|
|
25
|
+
are inherited from the base library.
|
|
26
|
+
|
|
27
|
+
= Installation =
|
|
28
|
+
|
|
29
|
+
| pip install robotframework-okw-web-selenium
|
|
30
|
+
|
|
31
|
+
= Import =
|
|
32
|
+
|
|
33
|
+
| Library okw_web_selenium.library.OkwWebSeleniumLibrary
|
|
34
|
+
|
|
35
|
+
= Beispiel =
|
|
36
|
+
|
|
37
|
+
| StartHost Chrome
|
|
38
|
+
| StartApp web/LoginApp
|
|
39
|
+
| SelectWindow LoginDialog
|
|
40
|
+
| SetValue Username admin
|
|
41
|
+
| SetValue Password secret
|
|
42
|
+
| ClickOn OK
|
|
43
|
+
|
|
44
|
+
= Abhaengigkeiten =
|
|
45
|
+
|
|
46
|
+
- ``robotframework-okw4robot >= 0.4.0``
|
|
47
|
+
- ``selenium >= 4.0``
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
ROBOT_LIBRARY_DOC_FORMAT = 'ROBOT'
|
|
51
|
+
ROBOT_LIBRARY_VERSION = '0.3.0'
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
"""Initialisiert die OkwWebSeleniumLibrary."""
|
|
55
|
+
super().__init__()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Chrome:
|
|
2
|
+
__self__:
|
|
3
|
+
class: okw_web_selenium.adapters.selenium_web.SeleniumWebAdapter
|
|
4
|
+
browser: chrome
|
|
5
|
+
|
|
6
|
+
Chrome:
|
|
7
|
+
|
|
8
|
+
URL:
|
|
9
|
+
class: okw_web_selenium.widgets.host.browsercontrol.UrlBar.UrlBar
|
|
10
|
+
locator: { virtual: true }
|
|
11
|
+
|
|
12
|
+
Maximize Window:
|
|
13
|
+
class: okw_web_selenium.widgets.host.browsercontrol.BrowserControl
|
|
14
|
+
locator: { virtual: true }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Firefox:
|
|
2
|
+
__self__:
|
|
3
|
+
class: okw_web_selenium.adapters.selenium_web.SeleniumWebAdapter
|
|
4
|
+
browser: firefox
|
|
5
|
+
|
|
6
|
+
Firefox:
|
|
7
|
+
|
|
8
|
+
URL:
|
|
9
|
+
class: okw_web_selenium.widgets.host.browsercontrol.UrlBar.UrlBar
|
|
10
|
+
locator: { virtual: true }
|
|
11
|
+
|
|
12
|
+
Maximize Window:
|
|
13
|
+
class: okw_web_selenium.widgets.host.browsercontrol.BrowserControl
|
|
14
|
+
locator: { virtual: true }
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""okw_web_selenium.widgets -- Selenium-Web-Widget-Klassen.
|
|
2
|
+
|
|
3
|
+
Neue WebSe_* Architektur (Delegation):
|
|
4
|
+
WebSe_Base -- gemeinsame Selenium-Logik
|
|
5
|
+
WebSe_TextField -- input[type=text], input[type=password], ...
|
|
6
|
+
WebSe_MultilineField -- textarea
|
|
7
|
+
WebSe_Button -- button, input[type=submit], ...
|
|
8
|
+
WebSe_Link -- a[href]
|
|
9
|
+
WebSe_Label -- span, div, p (read-only Text)
|
|
10
|
+
WebSe_CheckBox -- input[type=checkbox]
|
|
11
|
+
WebSe_ComboBox -- select, editierbare Combos
|
|
12
|
+
WebSe_RadioList -- input[type=radio] Gruppen
|
|
13
|
+
WebSe_ListBox -- select[multiple]
|
|
14
|
+
WebSe_Table -- table
|
|
15
|
+
|
|
16
|
+
Legacy widgets/common/ Klassen bleiben vorerst erhalten.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .webse_base import WebSe_Base
|
|
20
|
+
from .webse_textfield import WebSe_TextField
|
|
21
|
+
from .webse_multilinefield import WebSe_MultilineField
|
|
22
|
+
from .webse_button import WebSe_Button
|
|
23
|
+
from .webse_link import WebSe_Link
|
|
24
|
+
from .webse_label import WebSe_Label
|
|
25
|
+
from .webse_checkbox import WebSe_CheckBox
|
|
26
|
+
from .webse_combobox import WebSe_ComboBox
|
|
27
|
+
from .webse_radiolist import WebSe_RadioList
|
|
28
|
+
from .webse_listbox import WebSe_ListBox
|
|
29
|
+
from .webse_table import WebSe_Table
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"WebSe_Base",
|
|
33
|
+
"WebSe_TextField",
|
|
34
|
+
"WebSe_MultilineField",
|
|
35
|
+
"WebSe_Button",
|
|
36
|
+
"WebSe_Link",
|
|
37
|
+
"WebSe_Label",
|
|
38
|
+
"WebSe_CheckBox",
|
|
39
|
+
"WebSe_ComboBox",
|
|
40
|
+
"WebSe_RadioList",
|
|
41
|
+
"WebSe_ListBox",
|
|
42
|
+
"WebSe_Table",
|
|
43
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from okw4robot.widgets.okw_widget import OkwWidget
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UrlBar(OkwWidget):
|
|
5
|
+
|
|
6
|
+
def __init__(self, adapter, locator, **options):
|
|
7
|
+
super().__init__(adapter, locator, **options)
|
|
8
|
+
|
|
9
|
+
def okw_set_value(self, value):
|
|
10
|
+
self.log_current_method(f"Navigating to: {value}")
|
|
11
|
+
self.adapter.go_to(value)
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""WebSe_Base -- Gemeinsame Basis fuer alle Selenium-Web-Widgets.
|
|
2
|
+
|
|
3
|
+
Implementiert die ``OkwWidget``-Schnittstelle mit Selenium-spezifischer Logik:
|
|
4
|
+
- Zustandspruefungen (exists, visible, enabled, editable, focus, ...)
|
|
5
|
+
- Attribut-Zugriff (get_attribute, get_text, get_tooltip, get_label, get_placeholder)
|
|
6
|
+
- Sync-Helfer (_wait_before) aus dem alten BaseWidget uebernommen
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from okw4robot.widgets.okw_widget import OkwWidget
|
|
11
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WebSe_Base(OkwWidget):
|
|
15
|
+
"""Basis fuer alle Selenium-Web-Widgets."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, adapter, locator, **options):
|
|
18
|
+
super().__init__(adapter, locator, **options)
|
|
19
|
+
self.sl = adapter.sl # SeleniumLibrary-Instanz
|
|
20
|
+
|
|
21
|
+
# ------------------------------------------------------------------
|
|
22
|
+
# Interaktion (gemeinsam)
|
|
23
|
+
# ------------------------------------------------------------------
|
|
24
|
+
def okw_click(self):
|
|
25
|
+
self._wait_before('write')
|
|
26
|
+
self.adapter.click(self.locator)
|
|
27
|
+
|
|
28
|
+
def okw_double_click(self):
|
|
29
|
+
self._wait_before('write')
|
|
30
|
+
self.adapter.double_click(self.locator)
|
|
31
|
+
|
|
32
|
+
def okw_delete(self):
|
|
33
|
+
"""Loescht den Feldinhalt: clear + Backspace-Fallback."""
|
|
34
|
+
self._wait_before('write')
|
|
35
|
+
try:
|
|
36
|
+
self.adapter.clear_text(self.locator)
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
# Fallback: Ctrl+A + Delete
|
|
40
|
+
try:
|
|
41
|
+
self.adapter.press_keys(self.locator, "CTRL+a")
|
|
42
|
+
self.adapter.press_keys(self.locator, "DELETE")
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
# Werte lesen (gemeinsam)
|
|
48
|
+
# ------------------------------------------------------------------
|
|
49
|
+
def okw_get_text(self) -> str:
|
|
50
|
+
return self.adapter.get_text(self.locator) or ""
|
|
51
|
+
|
|
52
|
+
def okw_get_attribute(self, name: str) -> str:
|
|
53
|
+
val = self.adapter.get_attribute(self.locator, name)
|
|
54
|
+
return "" if val is None else str(val)
|
|
55
|
+
|
|
56
|
+
def okw_get_tooltip(self) -> str:
|
|
57
|
+
"""Liest Tooltip: bevorzugt 'title', Fallback 'aria-label'."""
|
|
58
|
+
try:
|
|
59
|
+
val = self.adapter.get_attribute(self.locator, 'title')
|
|
60
|
+
if val:
|
|
61
|
+
return val
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
try:
|
|
65
|
+
val = self.adapter.get_attribute(self.locator, 'aria-label')
|
|
66
|
+
if val:
|
|
67
|
+
return val
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
return ""
|
|
71
|
+
|
|
72
|
+
def okw_get_label(self) -> str:
|
|
73
|
+
"""Liest Label: aria-labelledby -> label[for] -> aria-label -> sichtbarer Text."""
|
|
74
|
+
a = self.adapter
|
|
75
|
+
# 1) aria-labelledby
|
|
76
|
+
try:
|
|
77
|
+
aria = a.get_attribute(self.locator, 'aria-labelledby')
|
|
78
|
+
if aria:
|
|
79
|
+
parts = [p for p in str(aria).split() if p]
|
|
80
|
+
texts = []
|
|
81
|
+
for pid in parts:
|
|
82
|
+
try:
|
|
83
|
+
texts.append(a.get_text({'id': pid}) or '')
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
joined = ' '.join(t.strip() for t in texts if t is not None)
|
|
87
|
+
if joined.strip():
|
|
88
|
+
return joined
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
# 2) <label for="id">
|
|
92
|
+
try:
|
|
93
|
+
elem_id = a.get_attribute(self.locator, 'id')
|
|
94
|
+
if elem_id:
|
|
95
|
+
try:
|
|
96
|
+
txt = a.get_text({'css': f'label[for="{elem_id}"]'})
|
|
97
|
+
if txt and txt.strip():
|
|
98
|
+
return txt
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
# 3) aria-label
|
|
104
|
+
try:
|
|
105
|
+
aria_label = a.get_attribute(self.locator, 'aria-label')
|
|
106
|
+
if aria_label:
|
|
107
|
+
return aria_label
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
# 4) Fallback: sichtbarer Text
|
|
111
|
+
try:
|
|
112
|
+
return a.get_text(self.locator) or ""
|
|
113
|
+
except Exception:
|
|
114
|
+
return ""
|
|
115
|
+
|
|
116
|
+
def okw_get_placeholder(self) -> str:
|
|
117
|
+
try:
|
|
118
|
+
ph = self.adapter.get_attribute(self.locator, 'placeholder')
|
|
119
|
+
return ph if ph is not None else ""
|
|
120
|
+
except Exception:
|
|
121
|
+
return ""
|
|
122
|
+
|
|
123
|
+
# ------------------------------------------------------------------
|
|
124
|
+
# Zustand (gemeinsam)
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
def okw_exists(self) -> bool:
|
|
127
|
+
return self.adapter.element_exists(self.locator)
|
|
128
|
+
|
|
129
|
+
def okw_is_visible(self) -> bool:
|
|
130
|
+
return self.adapter.is_visible(self.locator)
|
|
131
|
+
|
|
132
|
+
def okw_is_enabled(self) -> bool:
|
|
133
|
+
return self.adapter.is_enabled(self.locator)
|
|
134
|
+
|
|
135
|
+
def okw_is_editable(self) -> bool:
|
|
136
|
+
return self.adapter.is_editable(self.locator)
|
|
137
|
+
|
|
138
|
+
def okw_has_focus(self) -> bool:
|
|
139
|
+
return self.adapter.has_focus(self.locator)
|
|
140
|
+
|
|
141
|
+
def okw_is_focusable(self) -> bool:
|
|
142
|
+
return self.adapter.is_focusable(self.locator)
|
|
143
|
+
|
|
144
|
+
def okw_is_clickable(self) -> bool:
|
|
145
|
+
return self.adapter.is_clickable(self.locator)
|
|
146
|
+
|
|
147
|
+
def okw_set_focus(self):
|
|
148
|
+
self.adapter.focus(self.locator)
|
|
149
|
+
|
|
150
|
+
# ------------------------------------------------------------------
|
|
151
|
+
# Sync-Helfer (aus altem BaseWidget uebernommen)
|
|
152
|
+
# ------------------------------------------------------------------
|
|
153
|
+
def _get_global_sync(self, intent: str) -> dict:
|
|
154
|
+
bi = BuiltIn()
|
|
155
|
+
timeout = bi.get_variable_value(
|
|
156
|
+
'${OKW_SYNC_TIMEOUT_WRITE}' if intent == 'write' else '${OKW_SYNC_TIMEOUT_READ}',
|
|
157
|
+
default=10)
|
|
158
|
+
poll = bi.get_variable_value('${OKW_SYNC_POLL}', default=0.1)
|
|
159
|
+
scroll_def = bi.get_variable_value('${OKW_SYNC_SCROLL_INTO_VIEW}', default='YES')
|
|
160
|
+
editable_def = bi.get_variable_value('${OKW_SYNC_CHECK_EDITABLE}', default='YES')
|
|
161
|
+
busy = bi.get_variable_value(
|
|
162
|
+
'${OKW_BUSY_SELECTORS_WRITE}' if intent == 'write' else '${OKW_BUSY_SELECTORS_READ}',
|
|
163
|
+
default=None)
|
|
164
|
+
busy_list = []
|
|
165
|
+
if busy:
|
|
166
|
+
if isinstance(busy, (list, tuple)):
|
|
167
|
+
busy_list = list(busy)
|
|
168
|
+
else:
|
|
169
|
+
busy_list = [v.strip() for v in str(busy).split(',') if v.strip()]
|
|
170
|
+
return {
|
|
171
|
+
'timeout': float(timeout) if isinstance(timeout, (int, float)) else bi.convert_time(str(timeout)),
|
|
172
|
+
'poll': float(poll) if isinstance(poll, (int, float)) else bi.convert_time(str(poll)),
|
|
173
|
+
'exists': True,
|
|
174
|
+
'visible': True,
|
|
175
|
+
'enabled': True,
|
|
176
|
+
'editable': (
|
|
177
|
+
self.__class__.__name__ in ('WebSe_TextField', 'WebSe_MultilineField')
|
|
178
|
+
and str(editable_def).strip().upper() in ('YES', 'TRUE', '1')
|
|
179
|
+
) if intent == 'write' else False,
|
|
180
|
+
'scroll_into_view': (
|
|
181
|
+
str(scroll_def).strip().upper() in ('YES', 'TRUE', '1')
|
|
182
|
+
) if intent == 'write' else False,
|
|
183
|
+
'until_not_visible': busy_list,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
def _merge_wait(self, intent: str) -> dict:
|
|
187
|
+
cfg = self._get_global_sync(intent)
|
|
188
|
+
inst = (self.options.get('wait') or {}).get(intent, {})
|
|
189
|
+
for k, v in inst.items():
|
|
190
|
+
cfg[k] = v
|
|
191
|
+
return cfg
|
|
192
|
+
|
|
193
|
+
def _wait_before(self, intent: str):
|
|
194
|
+
cfg = self._merge_wait(intent)
|
|
195
|
+
timeout = float(cfg.get('timeout', 10))
|
|
196
|
+
poll = float(cfg.get('poll', 0.1))
|
|
197
|
+
end = time.time() + timeout
|
|
198
|
+
has_locator = bool(self.locator)
|
|
199
|
+
|
|
200
|
+
if has_locator:
|
|
201
|
+
# 1) exists
|
|
202
|
+
if cfg.get('exists', True):
|
|
203
|
+
ok = False
|
|
204
|
+
while time.time() < end:
|
|
205
|
+
if self.adapter.element_exists(self.locator):
|
|
206
|
+
ok = True
|
|
207
|
+
break
|
|
208
|
+
time.sleep(poll)
|
|
209
|
+
if not ok:
|
|
210
|
+
raise AssertionError('[Sync] Element does not exist')
|
|
211
|
+
|
|
212
|
+
# 2) scroll into view
|
|
213
|
+
if cfg.get('scroll_into_view', False):
|
|
214
|
+
try:
|
|
215
|
+
self.adapter.scroll_into_view(self.locator)
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
# 3) visible
|
|
220
|
+
if cfg.get('visible', True):
|
|
221
|
+
try:
|
|
222
|
+
self.adapter.wait_until_visible(self.locator, timeout=timeout)
|
|
223
|
+
except Exception:
|
|
224
|
+
raise AssertionError('[Sync] Element not visible')
|
|
225
|
+
|
|
226
|
+
# 4) enabled
|
|
227
|
+
if cfg.get('enabled', True):
|
|
228
|
+
ok = False
|
|
229
|
+
while time.time() < end:
|
|
230
|
+
if getattr(self.adapter, 'is_enabled', None) and self.adapter.is_enabled(self.locator):
|
|
231
|
+
ok = True
|
|
232
|
+
break
|
|
233
|
+
time.sleep(poll)
|
|
234
|
+
if not ok:
|
|
235
|
+
raise AssertionError('[Sync] Element not enabled')
|
|
236
|
+
|
|
237
|
+
# 5) editable
|
|
238
|
+
if cfg.get('editable', False):
|
|
239
|
+
ok = False
|
|
240
|
+
while time.time() < end:
|
|
241
|
+
if getattr(self.adapter, 'is_editable', None) and self.adapter.is_editable(self.locator):
|
|
242
|
+
ok = True
|
|
243
|
+
break
|
|
244
|
+
time.sleep(poll)
|
|
245
|
+
if not ok:
|
|
246
|
+
raise AssertionError('[Sync] Element not editable')
|
|
247
|
+
|
|
248
|
+
# 6) project waits (until_not_visible)
|
|
249
|
+
for sel in cfg.get('until_not_visible', []) or []:
|
|
250
|
+
try:
|
|
251
|
+
loc = sel if isinstance(sel, (str, dict)) else str(sel)
|
|
252
|
+
if isinstance(loc, str) and ':' not in loc:
|
|
253
|
+
loc = {'css': loc}
|
|
254
|
+
self.adapter.wait_until_not_visible(loc, timeout=timeout)
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""WebSe_Button -- Selenium-Implementierung fuer Buttons."""
|
|
2
|
+
|
|
3
|
+
from .webse_base import WebSe_Base
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WebSe_Button(WebSe_Base):
|
|
7
|
+
"""Button-Widget. Click und DoubleClick aus WebSe_Base genuegen."""
|
|
8
|
+
|
|
9
|
+
def okw_get_value(self) -> str:
|
|
10
|
+
"""Fuer Buttons: sichtbarer Text als 'Value'."""
|
|
11
|
+
return self.adapter.get_text(self.locator) or ""
|