robotframework-apex 0.0.2__tar.gz → 0.0.3__tar.gz
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.
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/PKG-INFO +2 -2
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/pyproject.toml +2 -2
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/pyproject.toml.orig +2 -2
- robotframework_apex-0.0.3/src/BrowserApex/browser.py +320 -0
- robotframework_apex-0.0.3/src/BrowserApex/keywords/__init__.py +18 -0
- robotframework_apex-0.0.3/src/BrowserApex/keywords/breadcrumb.py +32 -0
- robotframework_apex-0.0.3/src/BrowserApex/keywords/classicreport.py +133 -0
- robotframework_apex-0.0.3/src/BrowserApex/keywords/dialog.py +66 -0
- robotframework_apex-0.0.3/src/BrowserApex/keywords/field_commands.py +87 -0
- robotframework_apex-0.0.3/src/BrowserApex/keywords/login.py +45 -0
- robotframework_apex-0.0.3/src/BrowserApex/keywords/region.py +132 -0
- robotframework_apex-0.0.3/src/BrowserApex/keywords/tabs.py +61 -0
- robotframework_apex-0.0.3/src/BrowserApex/librarycomponent.py +12 -0
- robotframework_apex-0.0.3/src/BrowserApex/utils.py +14 -0
- robotframework_apex-0.0.3/src/BrowserApex/version.py +1 -0
- robotframework_apex-0.0.2/src/BrowserApex/browser.py +0 -547
- robotframework_apex-0.0.2/src/BrowserApex/keywords/__init__.py +0 -0
- robotframework_apex-0.0.2/src/BrowserApex/keywords/commands.py +0 -0
- robotframework_apex-0.0.2/src/BrowserApex/version.py +0 -1
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/LICENSE +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/README.md +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/__init__.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/__init__.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/commands/__init__.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/commands/config.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/commands/dump.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/commands/generate.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/commands/parse.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/commands/show.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/main.py +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/templates/custom-settings.template +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/templates/keywords.template +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/templates/pageobject.template +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/templates/pageobjects-resource.template +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/templates/rfapex.toml.template +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/templates/settings.template +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/templates/variables.template +0 -0
- {robotframework_apex-0.0.2 → robotframework_apex-0.0.3}/src/BrowserApex/cli/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robotframework-apex
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Robot Framework Browser extension library supporting Oracle APEX application using Page Object Model.
|
|
5
5
|
Author: Maurice Koster
|
|
6
6
|
Author-email: Maurice Koster <maurice@mauricekoster.com>
|
|
@@ -20,7 +20,7 @@ Requires-Dist: typer>=0.26.8
|
|
|
20
20
|
Requires-Dist: jinja2>=3.1.6
|
|
21
21
|
Requires-Dist: python-dotenv
|
|
22
22
|
Requires-Dist: parsimonious>=0.11.0
|
|
23
|
-
Requires-Dist: python-oracle-apex>=0.0.
|
|
23
|
+
Requires-Dist: python-oracle-apex>=0.0.1
|
|
24
24
|
Requires-Python: >=3.13
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
@@ -13,7 +13,7 @@ explicit = true
|
|
|
13
13
|
|
|
14
14
|
[project]
|
|
15
15
|
name = "robotframework-apex"
|
|
16
|
-
version = "0.0.
|
|
16
|
+
version = "0.0.3"
|
|
17
17
|
dependencies = [
|
|
18
18
|
"robotframework >= 7.1.1, < 9.0.0",
|
|
19
19
|
"robotframework-browser >= 20.0.0, < 21.0.0",
|
|
@@ -23,7 +23,7 @@ dependencies = [
|
|
|
23
23
|
"Jinja2 >= 3.1.6",
|
|
24
24
|
"python-dotenv",
|
|
25
25
|
"parsimonious>=0.11.0",
|
|
26
|
-
"python-oracle-apex>=0.0.
|
|
26
|
+
"python-oracle-apex>=0.0.1",
|
|
27
27
|
]
|
|
28
28
|
requires-python = ">= 3.13"
|
|
29
29
|
description = "Robot Framework Browser extension library supporting Oracle APEX application using Page Object Model."
|
|
@@ -8,7 +8,7 @@ module-name = "BrowserApex"
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "robotframework-apex"
|
|
10
10
|
|
|
11
|
-
version = "0.0.
|
|
11
|
+
version = "0.0.3"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"robotframework >= 7.1.1, < 9.0.0",
|
|
14
14
|
"robotframework-browser >= 20.0.0, < 21.0.0",
|
|
@@ -18,7 +18,7 @@ dependencies = [
|
|
|
18
18
|
"Jinja2 >= 3.1.6",
|
|
19
19
|
"python-dotenv",
|
|
20
20
|
"parsimonious>=0.11.0",
|
|
21
|
-
"python-oracle-apex>=0.0.
|
|
21
|
+
"python-oracle-apex>=0.0.1",
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Oracle APEX support
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
6
|
+
from robot.api.deco import not_keyword, library
|
|
7
|
+
from Browser import SelectAttribute, ElementState, Browser
|
|
8
|
+
from Browser.utils import PageLoadStates, logger
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from .keywords import (
|
|
12
|
+
Breadcrumb,
|
|
13
|
+
ClassicReport,
|
|
14
|
+
Dialog,
|
|
15
|
+
FieldCommand,
|
|
16
|
+
LoginTemplate,
|
|
17
|
+
Region,
|
|
18
|
+
Tab
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@library(scope='GLOBAL', auto_keywords=True)
|
|
23
|
+
class BrowserApex(Browser):
|
|
24
|
+
def __init__(self, **kwargs):
|
|
25
|
+
if 'test_date_format' in kwargs:
|
|
26
|
+
self.test_date_format = kwargs['test_date_format']
|
|
27
|
+
del kwargs['test_date_format']
|
|
28
|
+
else:
|
|
29
|
+
self.test_date_format = '%m/%d/%Y'
|
|
30
|
+
|
|
31
|
+
Browser.__init__(self, **kwargs)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
self.container = None
|
|
36
|
+
|
|
37
|
+
self.field_input_callbacks = {
|
|
38
|
+
'TextField': self.fill_text_field,
|
|
39
|
+
'Textarea': self.fill_textarea_field,
|
|
40
|
+
'TextFieldwithautocomplete': self.fill_text_autocomplete_field,
|
|
41
|
+
'NumberField': self.fill_number_field,
|
|
42
|
+
'Password': self.fill_password_field,
|
|
43
|
+
'SelectList': self.fill_select_field,
|
|
44
|
+
'RadioGroup': self.fill_radio_group,
|
|
45
|
+
'DatePicker': self.fill_date_picker,
|
|
46
|
+
'PopupLOV': self.fill_popup_lov,
|
|
47
|
+
}
|
|
48
|
+
self.field_get_callbacks = {
|
|
49
|
+
'Hidden': self.get_value_hidden,
|
|
50
|
+
'DisplayOnly': self.get_value_displayonly
|
|
51
|
+
}
|
|
52
|
+
self.field_check_callbacks = {
|
|
53
|
+
'DisplayOnly': self.check_display_only,
|
|
54
|
+
'TextField': self.check_text_field,
|
|
55
|
+
'SelectList': self.check_select_list,
|
|
56
|
+
'DatePicker': self.check_date_picker,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
self.locators = {}
|
|
60
|
+
|
|
61
|
+
self._field_commands = FieldCommand(self)
|
|
62
|
+
|
|
63
|
+
self.add_library_components([
|
|
64
|
+
Breadcrumb(self),
|
|
65
|
+
ClassicReport(self),
|
|
66
|
+
Dialog(self),
|
|
67
|
+
self._field_commands,
|
|
68
|
+
LoginTemplate(self),
|
|
69
|
+
Region(self),
|
|
70
|
+
Tab(self),
|
|
71
|
+
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@not_keyword
|
|
76
|
+
def get_locator(self, locator_name, replace_strings={}):
|
|
77
|
+
locator = self.locators.get(locator_name)
|
|
78
|
+
for k, v in replace_strings.items():
|
|
79
|
+
locator = locator.replace(k, v)
|
|
80
|
+
|
|
81
|
+
return locator
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@not_keyword
|
|
86
|
+
def fill_text_field(self, field_name, field_id, value, field_args):
|
|
87
|
+
print(f"*INFO* Filling text field '{field_name}' with value: {value}")
|
|
88
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
89
|
+
self.fill_text(element, str(value))
|
|
90
|
+
|
|
91
|
+
@not_keyword
|
|
92
|
+
def fill_text_autocomplete_field(self, field_name, field_id, value, field_args):
|
|
93
|
+
print(f"*INFO* Filling text field with autocomplete '{field_name}' with value: {value}")
|
|
94
|
+
element = self.get_element(f"{self.container} >> id={field_id} >> xpath=//input")
|
|
95
|
+
self.fill_text(element, str(value))
|
|
96
|
+
|
|
97
|
+
@not_keyword
|
|
98
|
+
def fill_textarea_field(self, field_name, field_id, value, field_args):
|
|
99
|
+
print(f"*INFO* Filling textarea field '{field_name}' with value: {value}")
|
|
100
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
101
|
+
self.fill_text(element, str(value))
|
|
102
|
+
|
|
103
|
+
@not_keyword
|
|
104
|
+
def fill_number_field(self, field_name, field_id, value, field_args):
|
|
105
|
+
print(f"*INFO* Filling text field '{field_name}' with value: {value}")
|
|
106
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
107
|
+
self.fill_text(element, str(value))
|
|
108
|
+
|
|
109
|
+
@not_keyword
|
|
110
|
+
def fill_password_field(self, field_name, field_id, value, field_args):
|
|
111
|
+
print(f"*INFO* Filling password field '{field_name}'")
|
|
112
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
113
|
+
self.fill_text(element, value)
|
|
114
|
+
|
|
115
|
+
@not_keyword
|
|
116
|
+
def fill_select_field(self, field_name, field_id, value, field_args):
|
|
117
|
+
print(f"*INFO* Filling select field '{field_name}' with value: {value}")
|
|
118
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
119
|
+
self.select_options_by(element, SelectAttribute.label, value)
|
|
120
|
+
|
|
121
|
+
@not_keyword
|
|
122
|
+
def fill_radio_group(self, field_name, field_id, value, field_args):
|
|
123
|
+
print(f"*INFO* Filling radio group '{field_name}' with value: {value}")
|
|
124
|
+
element = self.get_element(f"{self.container} >> id={field_id} >> xpath=//label[normalize-space(text())='{value}']")
|
|
125
|
+
self.click(element)
|
|
126
|
+
self.wait_for_elements_state(f"{self.container} >> id={field_id}", ElementState.stable)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@not_keyword
|
|
130
|
+
def fill_date_picker(self, field_name, field_id, value, field_args):
|
|
131
|
+
print(f"*INFO* Filling date picker '{field_name}' with value: {value}")
|
|
132
|
+
datepicker_element = self.get_element(f"{self.container} >> id={field_id}")
|
|
133
|
+
display_as = 'native'
|
|
134
|
+
attrs = self.get_attribute_names(datepicker_element)
|
|
135
|
+
if 'display-as' in attrs:
|
|
136
|
+
display_as = self.get_attribute(datepicker_element, 'display-as')
|
|
137
|
+
|
|
138
|
+
match display_as:
|
|
139
|
+
case 'native':
|
|
140
|
+
date_format = BuiltIn().get_variable_value("${datepicker_native_dateformat}", self.test_date_format)
|
|
141
|
+
case 'popup':
|
|
142
|
+
date_format = BuiltIn().get_variable_value("${datepicker_popup_dateformat}", self.test_date_format)
|
|
143
|
+
case _:
|
|
144
|
+
raise f"Unexpected 'display-as' attribute '{display_as}' of datapicker for {field_name}"
|
|
145
|
+
element = f"{datepicker_element} >> xpath=//input"
|
|
146
|
+
|
|
147
|
+
d = datetime.strptime(value, self.test_date_format)
|
|
148
|
+
print(f"*INFO* datepicker variant: {display_as}. Using date format to type text: {date_format}")
|
|
149
|
+
self.type_text(element, d.strftime(format=date_format))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@not_keyword
|
|
153
|
+
def fill_popup_lov(self, field_name, field_id, value, field_args):
|
|
154
|
+
print(f"*INFO* Filling popup '{field_name}' with value: {value} args: {field_args}")
|
|
155
|
+
# direct input = <id>
|
|
156
|
+
# popup button = <id>_lov_btn
|
|
157
|
+
element = self.get_element(f"{self.container} >> id={field_id}_lov_btn")
|
|
158
|
+
self.click(element)
|
|
159
|
+
self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
160
|
+
|
|
161
|
+
# zoeken zit buiten de dialog (in hoofdpagina) in de DOM
|
|
162
|
+
old_prefix = self.set_selector_prefix("")
|
|
163
|
+
|
|
164
|
+
nr_retries = 5
|
|
165
|
+
while nr_retries > 0:
|
|
166
|
+
nr_retries -= 1
|
|
167
|
+
search_element = self.get_element(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
|
|
168
|
+
'>> xpath=//input[@aria-label="Zoeken"]')
|
|
169
|
+
self.clear_text(search_element)
|
|
170
|
+
BuiltIn().sleep(1)
|
|
171
|
+
self.wait_for_load_state(PageLoadStates.networkidle, 10)
|
|
172
|
+
self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
173
|
+
|
|
174
|
+
self.type_text(search_element, value)
|
|
175
|
+
BuiltIn().sleep(1)
|
|
176
|
+
self.wait_for_load_state(PageLoadStates.networkidle, 10)
|
|
177
|
+
self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
178
|
+
|
|
179
|
+
if 'manualentry' in field_args:
|
|
180
|
+
logger.info('PopupLOV: Entering manualentry')
|
|
181
|
+
search_element = self.get_element(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
|
|
182
|
+
'>> xpath=//input[@aria-label="Zoeken"]')
|
|
183
|
+
self.press_keys(search_element, 'Enter')
|
|
184
|
+
|
|
185
|
+
self.wait_for_load_state(PageLoadStates.networkidle, 10)
|
|
186
|
+
self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
187
|
+
|
|
188
|
+
else:
|
|
189
|
+
# Wait for results, when expecting a row to match
|
|
190
|
+
old_mode = self.set_strict_mode(False)
|
|
191
|
+
self.wait_for_elements_state(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
|
|
192
|
+
'>> xpath=//div[contains(@class, "a-PopupLOV-results")] '
|
|
193
|
+
'>> xpath=//tbody/tr')
|
|
194
|
+
self.set_strict_mode(True)
|
|
195
|
+
|
|
196
|
+
elements = self.get_elements(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
|
|
197
|
+
'>> xpath=//div[contains(@class, "a-PopupLOV-results")]'
|
|
198
|
+
f'>> xpath=//tbody/tr')
|
|
199
|
+
|
|
200
|
+
if len(elements) > 0:
|
|
201
|
+
element = elements[0]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
self.click(element)
|
|
205
|
+
self.wait_for_load_state(PageLoadStates.domcontentloaded, 10)
|
|
206
|
+
|
|
207
|
+
states = self.get_element_states(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
|
|
208
|
+
'>> xpath=//div[contains(@class, "a-PopupLOV-results")]')
|
|
209
|
+
|
|
210
|
+
if 'visible' not in states:
|
|
211
|
+
break
|
|
212
|
+
|
|
213
|
+
# TODO: try, until selected value holds in input
|
|
214
|
+
|
|
215
|
+
self.set_selector_prefix(old_prefix)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@not_keyword
|
|
219
|
+
def fill_fields(self, locator, field_definition, data):
|
|
220
|
+
self.container = locator
|
|
221
|
+
for key, value in data.items():
|
|
222
|
+
if key in field_definition:
|
|
223
|
+
field = field_definition[key]
|
|
224
|
+
if ':' not in field:
|
|
225
|
+
raise AssertionError(f"Field '{key}' has invalid definition: '{field}'")
|
|
226
|
+
field_id, field_type, *field_args = field.split(':')
|
|
227
|
+
print(f'*INFO* field_id: {field_id} field_type: {field_type} field_args: {field_args}')
|
|
228
|
+
cb = self.field_input_callbacks.get(field_type)
|
|
229
|
+
|
|
230
|
+
used_value = self._field_commands.process_command(value)
|
|
231
|
+
|
|
232
|
+
if used_value:
|
|
233
|
+
if cb is None:
|
|
234
|
+
raise RuntimeError(f"Field type {field_type} not supported")
|
|
235
|
+
cb(key, field_id, used_value, field_args)
|
|
236
|
+
|
|
237
|
+
# set focus on container, to trigger validation on focus lost of field
|
|
238
|
+
self.focus(self.container)
|
|
239
|
+
self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
240
|
+
self.container = None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@not_keyword
|
|
244
|
+
def check_display_only(self, field_name, field_id, value):
|
|
245
|
+
print(f"*INFO* Checking DisplayOnly '{field_name}' with value: {value}")
|
|
246
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
247
|
+
real_value = self.get_text(element)
|
|
248
|
+
BuiltIn().should_be_equal_as_strings(value, real_value, f"Field values not equal: {value} <-> {real_value}")
|
|
249
|
+
|
|
250
|
+
@not_keyword
|
|
251
|
+
def check_text_field(self, field_name, field_id, value):
|
|
252
|
+
print(f"*INFO* Checking TextField '{field_name}' with value: {value}")
|
|
253
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
254
|
+
real_value = self.get_text(element)
|
|
255
|
+
BuiltIn().should_be_equal_as_strings(value, real_value, f"Field values not equal: {value} <-> {real_value}")
|
|
256
|
+
|
|
257
|
+
@not_keyword
|
|
258
|
+
def check_select_list(self, field_name, field_id, value):
|
|
259
|
+
print(f"*INFO* Checking SelectList '{field_name}' with value: {value}")
|
|
260
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
261
|
+
real_value = self.get_selected_options(element)
|
|
262
|
+
if len(real_value) != 1:
|
|
263
|
+
raise AssertionError("Expected 1 selected option")
|
|
264
|
+
BuiltIn().should_be_equal_as_strings(value, real_value[0], f"Field values not equal: {value} <-> {real_value}")
|
|
265
|
+
|
|
266
|
+
@not_keyword
|
|
267
|
+
def check_date_picker(self, field_name, field_id, value):
|
|
268
|
+
print(f"*INFO* Checking DatePicker '{field_name}' with value: {value}")
|
|
269
|
+
element = self.get_element(f"{self.container} >> id={field_id} >> //input")
|
|
270
|
+
real_value = self.get_text(element)
|
|
271
|
+
BuiltIn().should_be_equal_as_strings(value, real_value, f"Field values not equal: {value} <-> {real_value}")
|
|
272
|
+
|
|
273
|
+
@not_keyword
|
|
274
|
+
def check_fields(self, locator, field_definition, data):
|
|
275
|
+
self.container = locator
|
|
276
|
+
for key, value in data.items():
|
|
277
|
+
if key in field_definition:
|
|
278
|
+
field = field_definition[key]
|
|
279
|
+
if ':' not in field:
|
|
280
|
+
raise AssertionError(f"Field '{key}' has invalid definition: '{field}'")
|
|
281
|
+
field_id, field_type, *field_args = field.split(':')
|
|
282
|
+
print(f'*INFO* field_id: {field_id} field_type: {field_type} field_args: {field_args}')
|
|
283
|
+
cb = self.field_check_callbacks.get(field_type)
|
|
284
|
+
|
|
285
|
+
used_value = self._field_commands.process_command(value)
|
|
286
|
+
|
|
287
|
+
if used_value:
|
|
288
|
+
if cb is None:
|
|
289
|
+
raise RuntimeError(f"Field type {field_type} not supported")
|
|
290
|
+
cb(key, field_id, used_value)
|
|
291
|
+
self.container = None
|
|
292
|
+
|
|
293
|
+
@not_keyword
|
|
294
|
+
def get_value_hidden(self, field_id):
|
|
295
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
296
|
+
value = self.get_text(element)
|
|
297
|
+
return value
|
|
298
|
+
|
|
299
|
+
@not_keyword
|
|
300
|
+
def get_value_displayonly(self, field_id):
|
|
301
|
+
element = self.get_element(f"{self.container} >> id={field_id}")
|
|
302
|
+
value = self.get_text(element)
|
|
303
|
+
return value
|
|
304
|
+
|
|
305
|
+
@not_keyword
|
|
306
|
+
def get_field_value(self, container, field_definition, field_name):
|
|
307
|
+
self.container = container
|
|
308
|
+
field = field_definition[field_name]
|
|
309
|
+
if ':' not in field:
|
|
310
|
+
raise AssertionError(f"Field '{field_name}' has invalid definition: '{field}'")
|
|
311
|
+
field_id, field_type, *field_args = field.split(':')
|
|
312
|
+
print(f'*INFO* field_id: {field_id} field_type: {field_type} field_args: {field_args}')
|
|
313
|
+
cb = self.field_get_callbacks.get(field_type)
|
|
314
|
+
if cb is None:
|
|
315
|
+
raise AttributeError(f"*WARN* No getter for field type: {field_type}")
|
|
316
|
+
|
|
317
|
+
value = cb(field_id)
|
|
318
|
+
print(f"*INFO* get value from: '{field_name}' of type '{field_type}' returned: {value}")
|
|
319
|
+
self.container = None
|
|
320
|
+
return value
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .breadcrumb import Breadcrumb
|
|
2
|
+
from .classicreport import ClassicReport
|
|
3
|
+
from .dialog import Dialog
|
|
4
|
+
from .field_commands import FieldCommand
|
|
5
|
+
from .region import Region
|
|
6
|
+
from .tabs import Tab
|
|
7
|
+
from .login import LoginTemplate
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Breadcrumb",
|
|
12
|
+
"ClassicReport",
|
|
13
|
+
"Dialog",
|
|
14
|
+
"FieldCommand",
|
|
15
|
+
"LoginTemplate",
|
|
16
|
+
"Region",
|
|
17
|
+
"Tab"
|
|
18
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from Browser import AssertionOperator
|
|
2
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
3
|
+
from Browser.utils import PageLoadStates, logger
|
|
4
|
+
from robot.api.deco import keyword, not_keyword
|
|
5
|
+
from ..librarycomponent import LibraryComponent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Breadcrumb(LibraryComponent):
|
|
9
|
+
"""
|
|
10
|
+
Handling of Breadcrumb.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, library):
|
|
13
|
+
super().__init__(library)
|
|
14
|
+
|
|
15
|
+
self.library.locators.update({
|
|
16
|
+
'breadcrumb_container': "css=.t-BreadcrumbRegion",
|
|
17
|
+
'breadcrumb_button': "xpath=//button/span[contains(text(),'##TEXT##')]",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
@keyword(tags=('Apex', 'Breadcrumb'))
|
|
21
|
+
def breadcrumb_button(self, button):
|
|
22
|
+
"""
|
|
23
|
+
Click the button within the breadcrumb button region.
|
|
24
|
+
"""
|
|
25
|
+
logger.info(f"Pressing button with text '{button}' in breadcrumb")
|
|
26
|
+
|
|
27
|
+
container = self.library.get_locator('breadcrumb_container')
|
|
28
|
+
btn_locator = self.library.get_locator('breadcrumb_button', {'##TEXT##': button})
|
|
29
|
+
|
|
30
|
+
self.library.click(f"{container} >> {btn_locator}")
|
|
31
|
+
self.library.wait_for_load_state(PageLoadStates.networkidle, 10)
|
|
32
|
+
self.library.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from Browser import AssertionOperator
|
|
2
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
3
|
+
from Browser.utils import PageLoadStates, logger
|
|
4
|
+
from robot.api.deco import keyword, not_keyword
|
|
5
|
+
from ..librarycomponent import LibraryComponent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ClassicReport(LibraryComponent):
|
|
9
|
+
"""
|
|
10
|
+
Handling of Classic Report region.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, library):
|
|
13
|
+
super().__init__(library)
|
|
14
|
+
|
|
15
|
+
self.library.locators.update({
|
|
16
|
+
'report_container': "css=.t-Report",
|
|
17
|
+
'report_no_data_found': 'css=.nodatafound',
|
|
18
|
+
'report_table': "xpath=//table[@class='t-Report-report']",
|
|
19
|
+
'report_select_row': "xpath=//tbody/tr[##ROWNR##]",
|
|
20
|
+
'report_all_rows': "xpath=//tbody/tr",
|
|
21
|
+
'report_cell': "xpath=//td[@headers='##COL##']",
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
self.cell_check_callbacks = {
|
|
25
|
+
'PlainText': self.check_cell_plaintext
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@not_keyword
|
|
30
|
+
def _get_report_locator(self, report):
|
|
31
|
+
if type(report) is str:
|
|
32
|
+
report_name = report
|
|
33
|
+
else:
|
|
34
|
+
if 'name' in report:
|
|
35
|
+
report_name = report['name']
|
|
36
|
+
else:
|
|
37
|
+
raise AttributeError("No name in report")
|
|
38
|
+
|
|
39
|
+
return self.library.get_locator('report_container', {'##NAME##': report_name})
|
|
40
|
+
|
|
41
|
+
@not_keyword
|
|
42
|
+
def _check_report_visible(self, report, locator):
|
|
43
|
+
try:
|
|
44
|
+
element = self.library.get_element(f"{locator}")
|
|
45
|
+
self.library.get_element_states(element, AssertionOperator.contains, 'visible')
|
|
46
|
+
except:
|
|
47
|
+
raise AssertionError(f"Classic Report '{report}' not avaiable or visible")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@keyword(tags=('Apex', 'Classic Report'))
|
|
51
|
+
def select_report(self, report, parent=None):
|
|
52
|
+
if parent:
|
|
53
|
+
locator = self.library.get_element(f"{parent} >> {self._get_report_locator(report)}")
|
|
54
|
+
else:
|
|
55
|
+
locator = self.library.get_element(f"{self._get_report_locator(report)}")
|
|
56
|
+
|
|
57
|
+
self._check_report_visible(report, locator)
|
|
58
|
+
return locator
|
|
59
|
+
|
|
60
|
+
@keyword(tags=('Apex', 'Classic Report'))
|
|
61
|
+
def classic_report_select_row(self, locator, row_number):
|
|
62
|
+
table_locator = self.library.get_locator('report_table')
|
|
63
|
+
row_locator = self.library.get_locator('report_select_row', {"##ROWNR##": row_number})
|
|
64
|
+
self.library.click(f'{locator} >> {table_locator} >> {row_locator}')
|
|
65
|
+
self.library.wait_for_load_state(PageLoadStates.networkidle, 10)
|
|
66
|
+
self.library.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@not_keyword
|
|
70
|
+
def check_cell_plaintext(self, locator, column_name, column_id, value, *field_args):
|
|
71
|
+
logger.info(f"Checking PlainText '{column_name}' with value: {value}. Arguments: {field_args}")
|
|
72
|
+
cell_locator = self.library.get_locator('report_cell', {'##COL##': column_id})
|
|
73
|
+
self.library.get_text(locator + f" >> {cell_locator}",
|
|
74
|
+
AssertionOperator.equal, value)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@not_keyword
|
|
78
|
+
def _check_columns(self, locator, column_definition, data):
|
|
79
|
+
for key, value in data.items():
|
|
80
|
+
if key in column_definition:
|
|
81
|
+
field = column_definition[key]
|
|
82
|
+
if ':' not in field:
|
|
83
|
+
raise AssertionError(f"Column '{key}' has invalid definition: '{field}'")
|
|
84
|
+
field_id, field_type, *field_args = field.split(':')
|
|
85
|
+
logger.info(f'field_id: {field_id} field_type: {field_type} field_args: {field_args}')
|
|
86
|
+
cb = self.cell_check_callbacks.get(field_type)
|
|
87
|
+
|
|
88
|
+
used_value = self.library._field_commands.process_command(value)
|
|
89
|
+
|
|
90
|
+
if used_value:
|
|
91
|
+
if cb is None:
|
|
92
|
+
raise RuntimeError(f"Column type {field_type} not supported")
|
|
93
|
+
cb(locator, key, field_id, used_value, field_args)
|
|
94
|
+
|
|
95
|
+
@keyword(tags=('Apex', 'Classic Report'))
|
|
96
|
+
def classic_report_check_row(self, locator, columns_definition, row_number, data):
|
|
97
|
+
table_locator = self.library.get_locator('report_table')
|
|
98
|
+
row_locator = self.library.get_locator('report_select_row', {"##ROWNR##": row_number})
|
|
99
|
+
|
|
100
|
+
container = f'{locator} >> {table_locator} >> {row_locator}'
|
|
101
|
+
self._check_columns(container, columns_definition, data)
|
|
102
|
+
|
|
103
|
+
@keyword(tags=('Apex', 'Classic Report'))
|
|
104
|
+
def classic_report_no_data_found(self, locator, message=None):
|
|
105
|
+
"""
|
|
106
|
+
locator = Region
|
|
107
|
+
|
|
108
|
+
Check if region contains a Classic report, this is in case no data is found, there is no Classic Report.
|
|
109
|
+
"""
|
|
110
|
+
msg_locator=self.library.get_locator('report_no_data_found')
|
|
111
|
+
elems = self.library.get_elements(f"{locator} >> {msg_locator}")
|
|
112
|
+
old_time_out = self.library.set_browser_timeout(1)
|
|
113
|
+
BuiltIn().should_be_equal_as_numbers(len(elems), 1, "no-data-found section not found. Unexpected Classic Report found.")
|
|
114
|
+
self.library.set_browser_timeout(old_time_out)
|
|
115
|
+
|
|
116
|
+
if message:
|
|
117
|
+
self.library.get_text(f"{locator} >> {msg_locator}", AssertionOperator.contains, message)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@keyword(tags=('Apex', 'Classic Report'))
|
|
121
|
+
def classic_report_row_count(self, locator, expected_row_count=None):
|
|
122
|
+
self.library.wait_for_load_state(PageLoadStates.networkidle, 10)
|
|
123
|
+
self.library.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
124
|
+
table_locator = self.library.get_locator('report_table')
|
|
125
|
+
row_locator = self.library.get_locator('report_all_rows')
|
|
126
|
+
|
|
127
|
+
row_elements = self.library.get_elements(f'{locator} >> {table_locator} >> {row_locator}')
|
|
128
|
+
count = len(row_elements)
|
|
129
|
+
if expected_row_count is not None:
|
|
130
|
+
BuiltIn().should_be_equal_as_numbers(count, int(expected_row_count), "Number of report rows incorrect")
|
|
131
|
+
return count
|
|
132
|
+
|
|
133
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from Browser import AssertionOperator
|
|
2
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
3
|
+
from Browser.utils import PageLoadStates, logger, ElementState
|
|
4
|
+
from robot.api.deco import keyword, not_keyword
|
|
5
|
+
from ..librarycomponent import LibraryComponent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Dialog(LibraryComponent):
|
|
9
|
+
"""
|
|
10
|
+
Handling of Dialogs.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, library):
|
|
13
|
+
super().__init__(library)
|
|
14
|
+
|
|
15
|
+
self.library.locators.update({
|
|
16
|
+
'dialog_container': "xpath=//div[@role='dialog']",
|
|
17
|
+
'dialog_container_by_title': "xpath=//div[@role='dialog' and @aria-label='##TITLE##']",
|
|
18
|
+
#'dialog_container': "css=.ui-dialog--apex",
|
|
19
|
+
'dialog_button': "xpath=//button/span[contains(text(),'##TEXT##')]",
|
|
20
|
+
'dialog_close_button': "xpath=//button[contains(@class, 'ui-dialog-titlebar-close')]",
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
@keyword(tags=('Apex', 'Dialog'))
|
|
24
|
+
def select_dialog(self, title, parent=None):
|
|
25
|
+
container = self.library.get_locator('dialog_container_by_title', {'##TITLE##': title})
|
|
26
|
+
if parent:
|
|
27
|
+
container = f"{parent} >> {container}"
|
|
28
|
+
self.library.wait_for_elements_state(container, ElementState.visible)
|
|
29
|
+
return container
|
|
30
|
+
|
|
31
|
+
@keyword(tags=('Apex', 'Dialog'))
|
|
32
|
+
def dialog_button(self, button):
|
|
33
|
+
"""
|
|
34
|
+
Click the button within the dialog button region.
|
|
35
|
+
"""
|
|
36
|
+
logger.info(f"Pressing button with text '{button}' in dialog")
|
|
37
|
+
|
|
38
|
+
container = self.library.get_locator('dialog_container')
|
|
39
|
+
btn_locator = self.library.get_locator('dialog_button', {'##TEXT##': button})
|
|
40
|
+
|
|
41
|
+
btn = f"{container} >> {btn_locator}"
|
|
42
|
+
self.library.wait_for_elements_state(btn, ElementState.visible)
|
|
43
|
+
|
|
44
|
+
self.library.click(btn)
|
|
45
|
+
self.library.wait_for_load_state(PageLoadStates.networkidle, 10)
|
|
46
|
+
self.library.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
47
|
+
|
|
48
|
+
@keyword(tags=('Apex', 'Dialog'))
|
|
49
|
+
def dialog_close(self):
|
|
50
|
+
"""
|
|
51
|
+
Click the top-right close button of the dialog.
|
|
52
|
+
"""
|
|
53
|
+
logger.info(f"Pressing the dialog close button.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
container = self.library.get_locator('dialog_container')
|
|
57
|
+
btn_locator = self.library.get_locator('dialog_close_button')
|
|
58
|
+
|
|
59
|
+
old_prefix = self.library.set_selector_prefix(None)
|
|
60
|
+
btn = f"{container} >> {btn_locator}"
|
|
61
|
+
# self.library.wait_for_elements_state(btn, ElementState.visible)
|
|
62
|
+
|
|
63
|
+
self.library.click(btn)
|
|
64
|
+
self.library.wait_for_load_state(PageLoadStates.networkidle, 10)
|
|
65
|
+
self.library.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
|
|
66
|
+
self.library.set_selector_prefix(old_prefix)
|