robotframework-apex 0.0.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.
@@ -0,0 +1,3 @@
1
+ from .version import __VERSION__
2
+
3
+ from .browser import BrowserApex
BrowserApex/browser.py ADDED
@@ -0,0 +1,547 @@
1
+ """
2
+ Oracle APEX support
3
+ """
4
+
5
+ from robot.libraries.BuiltIn import BuiltIn
6
+ from robot.api.deco import keyword, not_keyword, library
7
+ from Browser import AssertionOperator, SelectAttribute, ElementState, Browser
8
+ from Browser.utils import PageLoadStates
9
+
10
+ selector_prefix = {
11
+ '/': 'xpath',
12
+ '.': 'css',
13
+ '#': 'id'
14
+ }
15
+
16
+ @library(scope='GLOBAL', auto_keywords=True)
17
+ class BrowserApex(Browser):
18
+ def __init__(self, **kwargs):
19
+ Browser.__init__(self, **kwargs)
20
+
21
+ # self.add_library_components()
22
+
23
+ self.container = None
24
+ self.classic_report_row = None
25
+
26
+ self.field_input_callbacks = {
27
+ 'TextField': self.fill_text_field,
28
+ 'NumberField': self.fill_number_field,
29
+ 'Password': self.fill_password_field,
30
+ 'SelectList': self.fill_select_field,
31
+ 'RadioGroup': self.fill_radio_group,
32
+ 'DatePicker': self.fill_date_picker,
33
+ 'PopupLOV': self.fill_popup_lov,
34
+ }
35
+ self.field_get_callbacks = {
36
+ 'Hidden': self.get_value_hidden,
37
+ 'DisplayOnly': self.get_value_displayonly
38
+ }
39
+ self.field_check_callbacks = {
40
+ 'DisplayOnly': self.check_display_only
41
+ }
42
+ self.field_commands = {
43
+ 'var': self.command_var
44
+ }
45
+
46
+ self.cell_check_callbacks = {
47
+ 'PlainText': self.check_cell_plaintext
48
+ }
49
+
50
+ self.locators = {
51
+ 'login_container': ".t-Login-region",
52
+ 'login_button': "//button/span[contains(text(),'##TEXT##')]",
53
+ 'block_container': "//div[@aria-label='##TEXT##']",
54
+ 'block_button': "//button/span[contains(text(),'##TEXT##')]",
55
+ 'page_button': "//button/span[contains(text(),'##TEXT##')]",
56
+ 'wizard_button': "//button/span[contains(text(),'##TEXT##')]",
57
+ 'tab_button': "//button/span[contains(text(),'##TEXT##')]",
58
+ }
59
+
60
+
61
+ @not_keyword
62
+ def command_var(self, *args):
63
+ print(f"*DEBUG* COMMAND VAR args: {args}")
64
+ value = BuiltIn().get_variable_value(args[0])
65
+ return value
66
+
67
+ @keyword
68
+ def register_field_commands(self, **commands_to_register):
69
+ if type(commands_to_register) is not dict:
70
+ raise AttributeError(f"Register Field Commands expect a dictionary as argument")
71
+
72
+ self.field_commands.update(commands_to_register)
73
+ print(f"*INFO* Test")
74
+
75
+ @not_keyword
76
+ def check_container_visible(self, container_name, container):
77
+ try:
78
+ container_prefix = selector_prefix.get(container[0],'id')
79
+ element = self.get_element(f"{container_prefix}={container}")
80
+ self.get_element_states(element, AssertionOperator.contains, 'visible')
81
+ except:
82
+ raise AssertionError(f"Container '{container_name}' not avaiable or visible")
83
+
84
+ @not_keyword
85
+ def check_data_in_definition(self, block_name, field_definition, data):
86
+
87
+ for key, _ in data.items():
88
+ if key not in field_definition:
89
+ print(f"*WARN* Data field '{key}' not found in definition of block '{block_name}'" )
90
+
91
+
92
+ @not_keyword
93
+ def check_button(self, block_name, button_fields, button_name):
94
+ if button_name not in button_fields:
95
+ raise AssertionError(f"Button '{button_name}' not in button definition for block '{block_name}'")
96
+
97
+ @not_keyword
98
+ def fill_text_field(self, field_name, field_id, value, field_args):
99
+ print(f"*INFO* Filling text field '{field_name}' with value: {value}")
100
+ container_prefix = selector_prefix.get(self.container[0],'id')
101
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id}")
102
+ self.fill_text(element, value)
103
+
104
+ @not_keyword
105
+ def fill_number_field(self, field_name, field_id, value, field_args):
106
+ print(f"*INFO* Filling text field '{field_name}' with value: {value}")
107
+ container_prefix = selector_prefix.get(self.container[0],'id')
108
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id}")
109
+ self.fill_text(element, value)
110
+
111
+ @not_keyword
112
+ def fill_password_field(self, field_name, field_id, value, field_args):
113
+ print(f"*INFO* Filling password field '{field_name}'")
114
+ container_prefix = selector_prefix.get(self.container[0],'id')
115
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id}")
116
+ self.fill_text(element, value)
117
+
118
+ @not_keyword
119
+ def fill_select_field(self, field_name, field_id, value, field_args):
120
+ print(f"*INFO* Filling select field '{field_name}' with value: {value}")
121
+ container_prefix = selector_prefix.get(self.container[0],'id')
122
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id}")
123
+ self.select_options_by(element, SelectAttribute.label, value)
124
+
125
+ @not_keyword
126
+ def fill_radio_group(self, field_name, field_id, value, field_args):
127
+ print(f"*INFO* Filling radio group '{field_name}' with value: {value}")
128
+ container_prefix = selector_prefix.get(self.container[0],'id')
129
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id} >> xpath=//label[normalize-space(text())='{value}']")
130
+ self.click(element)
131
+ self.wait_for_elements_state(f"{container_prefix}={self.container} >> id={field_id}", ElementState.stable)
132
+
133
+
134
+ @not_keyword
135
+ def fill_date_picker(self, field_name, field_id, value, field_args):
136
+ print(f"*INFO* Filling date picker '{field_name}' with value: {value}")
137
+ container_prefix = selector_prefix.get(self.container[0],'id')
138
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id} >> xpath=//input")
139
+ self.type_text(element, value)
140
+
141
+
142
+ @not_keyword
143
+ def fill_popup_lov(self, field_name, field_id, value, field_args):
144
+ print(f"*INFO* Filling popup '{field_name}' with value: {value}")
145
+ # direct input = <id>
146
+ # popup button = <id>_lov_btn
147
+ container_prefix = selector_prefix.get(self.container[0],'id')
148
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id}_lov_btn")
149
+ self.click(element)
150
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
151
+
152
+ # zoeken zit buiten de dialog (in hoofdpagina) in de DOM
153
+ old_prefix = self.set_selector_prefix("")
154
+
155
+ nr_retries = 5
156
+ while nr_retries > 0:
157
+ nr_retries -= 1
158
+ search_element = self.get_element(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
159
+ '>> xpath=//input[@aria-label="Zoeken"]')
160
+ self.clear_text(search_element)
161
+ BuiltIn().sleep(1)
162
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
163
+ self.wait_for_load_state(PageLoadStates.networkidle, 10)
164
+
165
+ self.type_text(search_element, value)
166
+ BuiltIn().sleep(1)
167
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
168
+ self.wait_for_load_state(PageLoadStates.networkidle, 10)
169
+
170
+ old_mode = self.set_strict_mode(False)
171
+ self.wait_for_elements_state(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
172
+ '>> xpath=//div[contains(@class, "a-PopupLOV-results")] '
173
+ '>> xpath=//tbody/tr')
174
+ self.set_strict_mode(True)
175
+
176
+ if 'manualentry' in field_args:
177
+ search_element = self.get_element(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
178
+ '>> xpath=//input[@aria-label="Zoeken"]')
179
+ self.press_keys(search_element, 'Enter')
180
+
181
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
182
+ self.wait_for_load_state(PageLoadStates.networkidle, 10)
183
+
184
+ else:
185
+ elements = self.get_elements(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
186
+ '>> xpath=//div[contains(@class, "a-PopupLOV-results")]'
187
+ f'>> xpath=//tbody/tr[contains(., "{value}")]')
188
+
189
+ if len(elements) > 0:
190
+ element = elements[0]
191
+
192
+ self.click(element)
193
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
194
+
195
+ states = self.get_element_states(f'xpath=//div[contains(@class, "a-PopupLOV-dialog") and contains(@id, "{field_id}")] '
196
+ '>> xpath=//div[contains(@class, "a-PopupLOV-results")]')
197
+
198
+ if 'visible' not in states:
199
+ break
200
+
201
+ # TODO: try, until selected value holds in input
202
+
203
+ self.set_selector_prefix(old_prefix)
204
+
205
+
206
+ @not_keyword
207
+ def process_command(self, value):
208
+ used_value = None
209
+ if value[0] in ['&', '!', '$']:
210
+ args = value[1:].split(':')
211
+ cmd_name = args.pop(0)
212
+ print(f"*INFO* command: {cmd_name} args: {args}")
213
+ if cmd_name in self.field_commands:
214
+ cmd = self.field_commands[cmd_name]
215
+ if type(cmd) is str:
216
+ # run as keyword
217
+ used_value = BuiltIn().run_keyword(cmd, *args)
218
+
219
+ else:
220
+ # run as function
221
+ used_value = cmd(*args)
222
+
223
+ else:
224
+ raise AttributeError(f"Field command `{cmd_name}` not found")
225
+ print(f"*INFO* command: {cmd_name} returned: {used_value}")
226
+
227
+ else:
228
+ used_value = value
229
+
230
+ return used_value
231
+
232
+ @not_keyword
233
+ def fill_fields(self, field_definition, data):
234
+ for key, value in data.items():
235
+ if key in field_definition:
236
+ field = field_definition[key]
237
+ if ':' not in field:
238
+ raise AssertionError(f"Field '{key}' has invalid definition: '{field}'")
239
+ field_id, field_type, *field_args = field.split(':')
240
+ print(f'*INFO* field_id: {field_id} field_type: {field_type} field_args: {field_args}')
241
+ cb = self.field_input_callbacks.get(field_type)
242
+
243
+ used_value = self.process_command(value)
244
+
245
+ if used_value:
246
+ if cb is None:
247
+ raise RuntimeError(f"Field type {field_type} not supported")
248
+ cb(key, field_id, used_value, field_args)
249
+
250
+ # set focus on container, to trigger validation on focus lost of field
251
+ self.focus(self.container)
252
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
253
+
254
+
255
+ @not_keyword
256
+ def check_display_only(self, field_name, field_id, value):
257
+ print(f"*INFO* Checking DisplayOnly '{field_name}' with value: {value}")
258
+ container_prefix = selector_prefix.get(self.container[0],'id')
259
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id}_DISPLAY")
260
+ real_value = self.get_text(element)
261
+ BuiltIn().should_be_equal_as_strings(value, real_value, f"Field values not equal: {value} <-> {real_value}")
262
+
263
+
264
+ @not_keyword
265
+ def check_fields(self, field_definition, data):
266
+ for key, value in data.items():
267
+ if key in field_definition:
268
+ field = field_definition[key]
269
+ if ':' not in field:
270
+ raise AssertionError(f"Field '{key}' has invalid definition: '{field}'")
271
+ field_id, field_type, *field_args = field.split(':')
272
+ print(f'*INFO* field_id: {field_id} field_type: {field_type} field_args: {field_args}')
273
+ cb = self.field_check_callbacks.get(field_type)
274
+
275
+ used_value = self.process_command(value)
276
+
277
+ if used_value:
278
+ if cb is None:
279
+ raise RuntimeError(f"Field type {field_type} not supported")
280
+ cb(key, field_id, used_value)
281
+
282
+ @keyword
283
+ def block_fill(self, block_name, field_definition, data):
284
+ container = self.locators.get('block_container').replace('##TEXT##', block_name)
285
+ self.check_container_visible(block_name, container)
286
+ self.check_data_in_definition(block_name, field_definition, data)
287
+ self.container = container
288
+ self.fill_fields(field_definition, data)
289
+ self.container = None
290
+
291
+ @keyword
292
+ def block_button(self, block_name, button_text):
293
+ container = self.locators.get('block_container').replace('##TEXT##', block_name)
294
+ self.check_container_visible(block_name, container)
295
+ self.container = container
296
+
297
+ locator = self.locators.get('block_button').replace('##TEXT##', button_text)
298
+
299
+ container_prefix = selector_prefix.get(self.container[0],'text')
300
+ button_prefix = selector_prefix.get(locator[0],'text')
301
+ element = self.get_element(f"{container_prefix}={self.container} >> {button_prefix}={locator}")
302
+
303
+ self.click(element)
304
+ self.wait_for_load_state(PageLoadStates.networkidle, 10)
305
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
306
+ self.container = None
307
+
308
+
309
+ @not_keyword
310
+ def get_value_hidden(self, field_id):
311
+ container_prefix = selector_prefix.get(self.container[0],'id')
312
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id}")
313
+ value = self.get_text(element)
314
+ return value
315
+
316
+ @not_keyword
317
+ def get_value_displayonly(self, field_id):
318
+ container_prefix = selector_prefix.get(self.container[0],'id')
319
+ element = self.get_element(f"{container_prefix}={self.container} >> id={field_id}_DISPLAY")
320
+ value = self.get_text(element)
321
+ return value
322
+
323
+ @not_keyword
324
+ def _get_field_value(self, field_definition, field_name):
325
+ field = field_definition[field_name]
326
+ if ':' not in field:
327
+ raise AssertionError(f"Field '{field_name}' has invalid definition: '{field}'")
328
+ field_id, field_type, *field_args = field.split(':')
329
+ print(f'*INFO* field_id: {field_id} field_type: {field_type} field_args: {field_args}')
330
+ cb = self.field_get_callbacks.get(field_type)
331
+ if cb is None:
332
+ raise AttributeError(f"*WARN* No getter for field type: {field_type}")
333
+
334
+ value = cb(field_id)
335
+ print(f"*INFO* get value from: '{field_name}' of type '{field_type}' returned: {value}")
336
+ return value
337
+
338
+
339
+ @keyword
340
+ def block_get_value(self, block_name, field_definition, field_name):
341
+ container = self.locators.get('block_container').replace('##TEXT##', block_name)
342
+ self.check_container_visible(block_name, container)
343
+ self.container = container
344
+
345
+ if field_name not in field_definition:
346
+ raise AttributeError(f"*WARN* Field '{field_name}' not in definition of block {block_name}")
347
+
348
+ self.container = container
349
+ value = self._get_field_value(field_definition, field_name)
350
+ self.container = None
351
+
352
+ return value
353
+
354
+
355
+ @keyword
356
+ def block_check(self, block_name, field_definition, data):
357
+ container = self.locators.get('block_container').replace('##TEXT##', block_name)
358
+ self.check_container_visible(block_name, container)
359
+ self.check_data_in_definition(block_name, field_definition, data)
360
+ self.container = container
361
+ self.check_fields(field_definition, data)
362
+ self.container = None
363
+
364
+
365
+ @not_keyword
366
+ def tab_select_helper(self, tab_name):
367
+
368
+ tab_header = f"//a/span[text()='{tab_name}']"
369
+ tab_container = f"//div[@aria-label='{tab_name}']"
370
+ self.check_container_visible(f"{tab_name}.header", tab_header)
371
+
372
+ header_prefix = selector_prefix.get(tab_header[0], 'id')
373
+ element = self.get_element(f"{header_prefix}={tab_header}")
374
+ self.click(element)
375
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
376
+
377
+ self.check_container_visible(f"{tab_name}.body", tab_container)
378
+ return tab_container
379
+
380
+
381
+ @keyword
382
+ def tab_fill(self, tab_name, field_definition, data):
383
+ tab_container = self.tab_select_helper(tab_name)
384
+
385
+ self.container = tab_container
386
+
387
+ self.fill_fields(field_definition, data)
388
+
389
+ self.container = None
390
+
391
+ @keyword
392
+ def tab_button(self, tab_name, button_text):
393
+ tab_container = self.tab_select_helper(tab_name)
394
+
395
+ self.container = tab_container
396
+ locator = self.locators.get('tab_button').replace('##TEXT##', button_text)
397
+
398
+ container_prefix = selector_prefix.get(self.container[0],'text')
399
+ button_prefix = selector_prefix.get(locator[0],'text')
400
+ element = self.get_element(f"{container_prefix}={self.container} >> {button_prefix}={locator}")
401
+
402
+ self.click(element)
403
+ self.wait_for_load_state(PageLoadStates.networkidle, 10)
404
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
405
+ self.container = None
406
+
407
+
408
+ @keyword
409
+ def wizard_button(self, button_text):
410
+ locator = self.locators.get('wizard_button').replace('##TEXT##', button_text)
411
+ button_prefix = selector_prefix.get(locator[0],'text')
412
+ element = self.get_element(f"{button_prefix}={locator}")
413
+ self.click(element)
414
+ self.wait_for_load_state(PageLoadStates.networkidle, 10)
415
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
416
+
417
+
418
+ @keyword
419
+ def page_button(self, button_text):
420
+ locator = self.locators.get('page_button').replace('##TEXT##', button_text)
421
+ button_prefix = selector_prefix.get(locator[0],'text')
422
+ element = self.get_element(f"{button_prefix}={locator}")
423
+
424
+ self.click(element)
425
+ self.wait_for_load_state(PageLoadStates.networkidle, 10)
426
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
427
+
428
+ @keyword
429
+ def page_get_value(self, field_definition, field_name):
430
+ self.container = ".t-Dialog-body"
431
+ if field_name not in field_definition:
432
+ raise AttributeError(f"*WARN* Field '{field_name}' not in definition of page")
433
+
434
+ value = self._get_field_value(field_definition, field_name)
435
+ self.container = None
436
+ return value
437
+
438
+ @not_keyword
439
+ def check_cell_plaintext(self, column_name, column_id, value):
440
+ print(f"*INFO* Checking PlainText '{column_name}' with value: {value}")
441
+ text = self.get_text(self.classic_report_row + f">> xpath=//td[@headers='{column_id}']")
442
+ BuiltIn().should_be_equal_as_strings(value, text, f"Field values not equal: {value} <-> {text}")
443
+
444
+
445
+ @not_keyword
446
+ def _check_columns(self, column_definition, data):
447
+ for key, value in data.items():
448
+ if key in column_definition:
449
+ field = column_definition[key]
450
+ if ':' not in field:
451
+ raise AssertionError(f"Column '{key}' has invalid definition: '{field}'")
452
+ field_id, field_type, *field_args = field.split(':')
453
+ print(f'*INFO* field_id: {field_id} field_type: {field_type} field_args: {field_args}')
454
+ cb = self.cell_check_callbacks.get(field_type)
455
+
456
+ used_value = self.process_command(value)
457
+
458
+ if used_value:
459
+ if cb is None:
460
+ raise RuntimeError(f"Column type {field_type} not supported")
461
+ cb(key, field_id, used_value)
462
+
463
+
464
+ @not_keyword
465
+ def _classic_report_select_row(self, rownumber):
466
+ container_prefix = selector_prefix.get(self.container[0],'text')
467
+ element = self.get_element(f'{container_prefix}={self.container} '
468
+ '>> xpath=//table[@class="t-Report-report"] '
469
+ f'>> xpath=//tbody/tr[{rownumber}]')
470
+ self.click(element)
471
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
472
+
473
+ @not_keyword
474
+ def _classic_report_check_row(self, columns_definition, rownumber, data):
475
+ container_prefix = selector_prefix.get(self.container[0],'text')
476
+ row_element = self.get_element(f'{container_prefix}={self.container} '
477
+ '>> xpath=//table[@class="t-Report-report"] '
478
+ f'>> xpath=//tbody/tr[{rownumber}]')
479
+
480
+ self.classic_report_row = row_element
481
+ self._check_columns(columns_definition, data)
482
+ self.classic_report_row = None
483
+
484
+
485
+ @keyword
486
+ def block_classic_report_select_row(self, block_name, rownumber):
487
+ container = self.locators.get('block_container').replace('##TEXT##', block_name)
488
+ self.check_container_visible(block_name, container)
489
+
490
+ self.container = container
491
+ self._classic_report_select_row(rownumber)
492
+
493
+ self.container = None
494
+
495
+ @keyword
496
+ def block_classic_report_check_row(self, block_name, tab_columns_definition, rownumber, data):
497
+ container = self.locators.get('block_container').replace('##TEXT##', block_name)
498
+
499
+ self.container = container
500
+ self._classic_report_check_row(tab_columns_definition, rownumber, data)
501
+ self.container = None
502
+
503
+
504
+ @keyword
505
+ def tab_classic_report_select_row(self, tab_name, rownumber):
506
+ tab_container = self.tab_select_helper(tab_name)
507
+
508
+ self.container = tab_container
509
+ self._classic_report_select_row(rownumber)
510
+ self.container = None
511
+
512
+ @keyword
513
+ def tab_classic_report_check_row(self, tab_name, tab_columns_definition, rownumber, data):
514
+ tab_container = self.tab_select_helper(tab_name)
515
+
516
+ self.container = tab_container
517
+ self._classic_report_check_row(tab_columns_definition, rownumber, data)
518
+ self.container = None
519
+
520
+ # Template: Login
521
+ @keyword
522
+ def login_fill(self, block_name, field_definition, data):
523
+ container = self.locators.get('login_container').replace('##TEXT##', block_name)
524
+
525
+ self.check_container_visible(block_name, container)
526
+ self.check_data_in_definition(block_name, field_definition, data)
527
+ self.container = container
528
+ self.fill_fields(field_definition, data)
529
+ self.container = None
530
+
531
+ @keyword
532
+ def login_button(self, block_name, button_text):
533
+
534
+ container = self.locators.get('login_container').replace('##TEXT##', block_name)
535
+ self.check_container_visible(block_name, container)
536
+ self.container = container
537
+
538
+ locator = self.locators.get('login_button').replace('##TEXT##', button_text)
539
+
540
+ container_prefix = selector_prefix.get(self.container[0],'text')
541
+ button_prefix = selector_prefix.get(locator[0],'text')
542
+ element = self.get_element(f"{container_prefix}={self.container} >> {button_prefix}={locator}")
543
+
544
+ self.click(element)
545
+ self.wait_for_load_state(PageLoadStates.networkidle, 10)
546
+ self.wait_for_load_state(PageLoadStates.domcontentloaded, 1)
547
+ self.container = None
File without changes
@@ -0,0 +1,4 @@
1
+ from .objects import *
2
+
3
+ from .grammar import parse_apex_file
4
+ from .reader import parse_yaml_file