robotframework-apex 0.0.2__tar.gz → 0.0.4__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.
Files changed (38) hide show
  1. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/PKG-INFO +2 -2
  2. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/pyproject.toml +2 -2
  3. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/pyproject.toml.orig +2 -2
  4. robotframework_apex-0.0.4/src/BrowserApex/browser.py +320 -0
  5. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/commands/show.py +2 -2
  6. robotframework_apex-0.0.4/src/BrowserApex/keywords/__init__.py +18 -0
  7. robotframework_apex-0.0.4/src/BrowserApex/keywords/breadcrumb.py +32 -0
  8. robotframework_apex-0.0.4/src/BrowserApex/keywords/classicreport.py +133 -0
  9. robotframework_apex-0.0.4/src/BrowserApex/keywords/dialog.py +66 -0
  10. robotframework_apex-0.0.4/src/BrowserApex/keywords/field_commands.py +87 -0
  11. robotframework_apex-0.0.4/src/BrowserApex/keywords/login.py +45 -0
  12. robotframework_apex-0.0.4/src/BrowserApex/keywords/region.py +132 -0
  13. robotframework_apex-0.0.4/src/BrowserApex/keywords/tabs.py +61 -0
  14. robotframework_apex-0.0.4/src/BrowserApex/librarycomponent.py +12 -0
  15. robotframework_apex-0.0.4/src/BrowserApex/utils.py +14 -0
  16. robotframework_apex-0.0.4/src/BrowserApex/version.py +1 -0
  17. robotframework_apex-0.0.2/src/BrowserApex/browser.py +0 -547
  18. robotframework_apex-0.0.2/src/BrowserApex/keywords/__init__.py +0 -0
  19. robotframework_apex-0.0.2/src/BrowserApex/keywords/commands.py +0 -0
  20. robotframework_apex-0.0.2/src/BrowserApex/version.py +0 -1
  21. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/LICENSE +0 -0
  22. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/README.md +0 -0
  23. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/__init__.py +0 -0
  24. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/__init__.py +0 -0
  25. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/commands/__init__.py +0 -0
  26. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/commands/config.py +0 -0
  27. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/commands/dump.py +0 -0
  28. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/commands/generate.py +0 -0
  29. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/commands/parse.py +0 -0
  30. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/main.py +0 -0
  31. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/templates/custom-settings.template +0 -0
  32. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/templates/keywords.template +0 -0
  33. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/templates/pageobject.template +0 -0
  34. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/templates/pageobjects-resource.template +0 -0
  35. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/templates/rfapex.toml.template +0 -0
  36. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/templates/settings.template +0 -0
  37. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/src/BrowserApex/cli/templates/variables.template +0 -0
  38. {robotframework_apex-0.0.2 → robotframework_apex-0.0.4}/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.2
3
+ Version: 0.0.4
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.0
23
+ Requires-Dist: python-oracle-apex>=0.0.4
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.2"
16
+ version = "0.0.4"
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.0",
26
+ "python-oracle-apex>=0.0.4",
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.2"
11
+ version = "0.0.4"
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.0",
21
+ "python-oracle-apex>=0.0.4",
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
@@ -5,7 +5,7 @@ from rich.table import Table
5
5
  from pathlib import Path
6
6
 
7
7
  from BrowserApex.cli.main import app
8
- from python_oracle_apex import parse_yaml_file, parse_apex_file
8
+ from python_oracle_apex import parse_page_file, parse_apex_file
9
9
 
10
10
  @app.command(name='show')
11
11
  def project_show(
@@ -21,7 +21,7 @@ def project_show(
21
21
  case '.apx':
22
22
  page = parse_apex_file(fn)
23
23
  case '.yaml' | '.yml':
24
- page = parse_yaml_file(fn)
24
+ page = parse_page_file(fn)
25
25
  case _:
26
26
  raise RuntimeWarning("Unsupported file")
27
27
 
@@ -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)