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.
Files changed (30) hide show
  1. okw_web_selenium/__init__.py +3 -0
  2. okw_web_selenium/adapters/__init__.py +0 -0
  3. okw_web_selenium/adapters/selenium_web.py +209 -0
  4. okw_web_selenium/keywords/__init__.py +0 -0
  5. okw_web_selenium/keywords/web_keywords.py +28 -0
  6. okw_web_selenium/library.py +55 -0
  7. okw_web_selenium/locators/Chrome.yaml +14 -0
  8. okw_web_selenium/locators/Firefox.yaml +14 -0
  9. okw_web_selenium/locators/__init__.py +0 -0
  10. okw_web_selenium/widgets/__init__.py +43 -0
  11. okw_web_selenium/widgets/host/__init__.py +0 -0
  12. okw_web_selenium/widgets/host/browsercontrol/BrowserControl.py +10 -0
  13. okw_web_selenium/widgets/host/browsercontrol/UrlBar.py +11 -0
  14. okw_web_selenium/widgets/host/browsercontrol/__init__.py +2 -0
  15. okw_web_selenium/widgets/webse_base.py +256 -0
  16. okw_web_selenium/widgets/webse_button.py +11 -0
  17. okw_web_selenium/widgets/webse_checkbox.py +20 -0
  18. okw_web_selenium/widgets/webse_combobox.py +103 -0
  19. okw_web_selenium/widgets/webse_label.py +10 -0
  20. okw_web_selenium/widgets/webse_link.py +11 -0
  21. okw_web_selenium/widgets/webse_listbox.py +38 -0
  22. okw_web_selenium/widgets/webse_multilinefield.py +11 -0
  23. okw_web_selenium/widgets/webse_radiolist.py +114 -0
  24. okw_web_selenium/widgets/webse_table.py +90 -0
  25. okw_web_selenium/widgets/webse_textfield.py +21 -0
  26. robotframework_okw_web_selenium-0.3.0.dist-info/METADATA +222 -0
  27. robotframework_okw_web_selenium-0.3.0.dist-info/RECORD +30 -0
  28. robotframework_okw_web_selenium-0.3.0.dist-info/WHEEL +5 -0
  29. robotframework_okw_web_selenium-0.3.0.dist-info/licenses/LICENSE +36 -0
  30. robotframework_okw_web_selenium-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ from .library import OkwWebSeleniumLibrary
2
+
3
+ __all__ = ["OkwWebSeleniumLibrary"]
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,10 @@
1
+ from okw4robot.widgets.okw_widget import OkwWidget
2
+
3
+
4
+ class BrowserControl(OkwWidget):
5
+
6
+ def __init__(self, adapter, locator, **options):
7
+ super().__init__(adapter, locator, **options)
8
+
9
+ def okw_click(self):
10
+ self.adapter.maximize_window()
@@ -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,2 @@
1
+ from .BrowserControl import BrowserControl
2
+ from .UrlBar import UrlBar
@@ -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 ""