robo_appian 0.0.27__py3-none-any.whl → 0.0.34__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.
- robo_appian/__init__.py +4 -2
- robo_appian/components/ButtonUtils.py +127 -54
- robo_appian/components/DateUtils.py +54 -20
- robo_appian/components/DropdownUtils.py +61 -61
- robo_appian/components/InputUtils.py +51 -19
- robo_appian/components/LabelUtils.py +82 -26
- robo_appian/components/LinkUtils.py +58 -23
- robo_appian/components/SearchDropdownUtils.py +101 -47
- robo_appian/components/SearchInputUtils.py +89 -43
- robo_appian/components/TabUtils.py +71 -24
- robo_appian/components/TableUtils.py +68 -44
- robo_appian/controllers/ComponentDriver.py +65 -16
- robo_appian/exceptions/MyCustomError.py +13 -1
- robo_appian/utils/BrowserUtils.py +55 -16
- robo_appian/utils/ComponentUtils.py +102 -56
- robo_appian/utils/RoboUtils.py +64 -0
- {robo_appian-0.0.27.dist-info → robo_appian-0.0.34.dist-info}/METADATA +4 -2
- robo_appian-0.0.34.dist-info/RECORD +20 -0
- robo_appian-0.0.34.dist-info/licenses/LICENSE +201 -0
- robo_appian/components/__init__.py +0 -22
- robo_appian/controllers/__init__.py +0 -0
- robo_appian/exceptions/__init__.py +0 -0
- robo_appian/utils/__init__.py +0 -0
- robo_appian-0.0.27.dist-info/RECORD +0 -23
- robo_appian-0.0.27.dist-info/licenses/LICENSE +0 -21
- {robo_appian-0.0.27.dist-info → robo_appian-0.0.34.dist-info}/WHEEL +0 -0
|
@@ -1,63 +1,109 @@
|
|
|
1
1
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
2
|
-
from selenium.webdriver.common.by import By
|
|
3
|
-
from selenium.webdriver.support import expected_conditions as EC
|
|
4
2
|
from robo_appian.components.InputUtils import InputUtils
|
|
5
3
|
from robo_appian.utils.ComponentUtils import ComponentUtils
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
class SearchInputUtils:
|
|
9
7
|
"""
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
Search and select from searchable input components in Appian UI.
|
|
9
|
+
|
|
10
|
+
Similar to SearchDropdownUtils, but for input components that support filtering/search.
|
|
11
|
+
Finds the input by label, types a search term, waits for matching options in a dropdown list,
|
|
12
|
+
and selects the option.
|
|
13
|
+
|
|
14
|
+
All methods follow the wait-first pattern: pass WebDriverWait as the first argument.
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
>>> from robo_appian import SearchInputUtils
|
|
18
|
+
>>> # Select by exact label match
|
|
19
|
+
>>> SearchInputUtils.selectSearchDropdownByLabelText(wait, "Employee Name", "John Doe")
|
|
20
|
+
>>> # Select by partial label match
|
|
21
|
+
>>> SearchInputUtils.selectSearchDropdownByPartialLabelText(wait, "Employee", "John")
|
|
22
|
+
|
|
23
|
+
Note:
|
|
24
|
+
- Searchable inputs often appear in Appian forms for employee/user selection
|
|
25
|
+
- Uses ARIA combobox and listbox patterns for element discovery
|
|
26
|
+
- Waits for listbox options to populate after typing search term
|
|
20
27
|
"""
|
|
21
28
|
|
|
22
29
|
@staticmethod
|
|
23
|
-
def __findSearchInputComponentsByLabelPathAndSelectValue(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return input_components
|
|
30
|
+
def __findSearchInputComponentsByLabelPathAndSelectValue(
|
|
31
|
+
wait: WebDriverWait, xpath: str, value: str
|
|
32
|
+
):
|
|
33
|
+
|
|
34
|
+
search_input_component = ComponentUtils.waitForComponentToBeVisibleByXpath(
|
|
35
|
+
wait, xpath
|
|
36
|
+
)
|
|
37
|
+
attribute: str = "aria-controls"
|
|
38
|
+
dropdown_list_id = search_input_component.get_attribute(attribute)
|
|
39
|
+
if dropdown_list_id:
|
|
40
|
+
InputUtils._setValueByComponent(wait, search_input_component, value)
|
|
41
|
+
xpath = f'.//ul[@id="{dropdown_list_id}" and @role="listbox" ]/li[@role="option" and @tabindex="-1" and ./div/div/div/div/div/div/p[normalize-space(.)="{value}"][1]]'
|
|
42
|
+
drop_down_item = ComponentUtils.waitForComponentToBeVisibleByXpath(
|
|
43
|
+
wait, xpath
|
|
44
|
+
)
|
|
45
|
+
ComponentUtils.click(wait, drop_down_item)
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Search input component with label '{search_input_component.text}' does not have 'aria-controls' attribute."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return search_input_component
|
|
46
52
|
|
|
47
53
|
@staticmethod
|
|
48
|
-
def __selectSearchInputComponentsByPartialLabelText(
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
def __selectSearchInputComponentsByPartialLabelText(
|
|
55
|
+
wait: WebDriverWait, label: str, value: str
|
|
56
|
+
):
|
|
57
|
+
xpath = f'.//div[./div/span[contains(normalize-space(.)="{label}"]]/div/div/div/input[@role="combobox"]'
|
|
58
|
+
SearchInputUtils.__findSearchInputComponentsByLabelPathAndSelectValue(
|
|
59
|
+
wait, xpath, value
|
|
60
|
+
)
|
|
51
61
|
|
|
52
62
|
@staticmethod
|
|
53
|
-
def __selectSearchInputComponentsByLabelText(
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
def __selectSearchInputComponentsByLabelText(
|
|
64
|
+
wait: WebDriverWait, label: str, value: str
|
|
65
|
+
):
|
|
66
|
+
xpath = f'.//div[./div/span[normalize-space(translate(., "\u00a0", " "))="{label}"]]/div/div/div/input[@role="combobox"]'
|
|
67
|
+
SearchInputUtils.__findSearchInputComponentsByLabelPathAndSelectValue(
|
|
68
|
+
wait, xpath, value
|
|
69
|
+
)
|
|
56
70
|
|
|
57
71
|
@staticmethod
|
|
58
72
|
def selectSearchDropdownByLabelText(wait: WebDriverWait, label: str, value: str):
|
|
73
|
+
"""
|
|
74
|
+
Select a value from a search input using exact label match.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
wait: WebDriverWait instance.
|
|
78
|
+
label: Exact visible label text of the search input.
|
|
79
|
+
value: Exact text of the option to select from the dropdown.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
None
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
>>> SearchInputUtils.selectSearchDropdownByLabelText(wait, "Employee Name", "John Doe")
|
|
86
|
+
"""
|
|
59
87
|
SearchInputUtils.__selectSearchInputComponentsByLabelText(wait, label, value)
|
|
60
88
|
|
|
61
89
|
@staticmethod
|
|
62
|
-
def selectSearchDropdownByPartialLabelText(
|
|
63
|
-
|
|
90
|
+
def selectSearchDropdownByPartialLabelText(
|
|
91
|
+
wait: WebDriverWait, label: str, value: str
|
|
92
|
+
):
|
|
93
|
+
"""
|
|
94
|
+
Select a value from a search input using partial label match.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
wait: WebDriverWait instance.
|
|
98
|
+
label: Partial visible label text (uses contains matching).
|
|
99
|
+
value: Exact text of the option to select from the dropdown.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
None
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
>>> SearchInputUtils.selectSearchDropdownByPartialLabelText(wait, "Employee", "John")
|
|
106
|
+
"""
|
|
107
|
+
SearchInputUtils.__selectSearchInputComponentsByPartialLabelText(
|
|
108
|
+
wait, label, value
|
|
109
|
+
)
|
|
@@ -7,47 +7,94 @@ from robo_appian.utils.ComponentUtils import ComponentUtils
|
|
|
7
7
|
|
|
8
8
|
class TabUtils:
|
|
9
9
|
"""
|
|
10
|
-
|
|
11
|
-
Example usage:
|
|
12
|
-
from selenium import webdriver
|
|
13
|
-
from selenium.webdriver.support.ui import WebDriverWait
|
|
14
|
-
from robo_appian.components.TabUtils import TabUtils
|
|
10
|
+
Select and check tab components in Appian UI.
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
Find and click tabs to navigate between sections within a single page. Appian often uses
|
|
13
|
+
tabs to organize related content. Automatically waits for clickability and uses ActionChains
|
|
14
|
+
for reliable interaction.
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
selected_tab = TabUtils.findSelectedTabByLabelText(wait, "Tab Label")
|
|
16
|
+
All methods follow the wait-first pattern: pass WebDriverWait as the first argument.
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
Examples:
|
|
19
|
+
>>> from robo_appian import TabUtils
|
|
20
|
+
>>> TabUtils.selectTabByLabelText(wait, "Details") # Click a tab
|
|
21
|
+
>>> tab = TabUtils.findTabByLabelText(wait, "History") # Get tab element
|
|
22
|
+
>>> is_selected = TabUtils.checkTabSelectedByLabelText(wait, "Active") # Check state
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
Note:
|
|
25
|
+
- Tab navigation triggers content reloads; be ready to wait for new elements
|
|
26
|
+
- Tab labels are stored in nested divs with role="link" and semantic text
|
|
26
27
|
"""
|
|
27
|
-
@staticmethod
|
|
28
|
-
def __click(wait: WebDriverWait, component: WebElement, label: str):
|
|
29
|
-
try:
|
|
30
|
-
component = wait.until(EC.element_to_be_clickable(component))
|
|
31
|
-
component.click()
|
|
32
|
-
except Exception:
|
|
33
|
-
raise Exception(f"Tab with label '{label}' is not clickable.")
|
|
34
28
|
|
|
35
29
|
@staticmethod
|
|
36
30
|
def findTabByLabelText(wait: WebDriverWait, label: str) -> WebElement:
|
|
31
|
+
"""
|
|
32
|
+
Find a tab element by its exact visible label.
|
|
33
|
+
|
|
34
|
+
Returns the tab element (useful for chaining or advanced inspection).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
wait: WebDriverWait instance.
|
|
38
|
+
label: Exact visible label text of the tab (e.g., "Details", "History").
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
WebElement: The tab element.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
TimeoutException: If tab not found within timeout.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
>>> tab = TabUtils.findTabByLabelText(wait, "Details")
|
|
48
|
+
"""
|
|
37
49
|
xpath = f'//div/div[@role="link" ]/div/div/div/div/div/p[normalize-space(.)="{label}"]'
|
|
38
|
-
|
|
39
|
-
component = wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))
|
|
40
|
-
except Exception:
|
|
41
|
-
raise Exception(f"Tab with label '{label}' not found.")
|
|
50
|
+
component = wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))
|
|
42
51
|
return component
|
|
43
52
|
|
|
44
53
|
@staticmethod
|
|
45
54
|
def selectTabByLabelText(wait: WebDriverWait, label: str):
|
|
55
|
+
"""
|
|
56
|
+
Click a tab to navigate to it by its exact visible label.
|
|
57
|
+
|
|
58
|
+
Finds and clicks the tab. After clicking, content in the tab panel will load;
|
|
59
|
+
be ready to wait for elements within the new tab.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
wait: WebDriverWait instance.
|
|
63
|
+
label: Exact visible label text of the tab to select.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
None
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
TimeoutException: If tab not found or not clickable within timeout.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
>>> TabUtils.selectTabByLabelText(wait, "Details")
|
|
73
|
+
>>> TabUtils.selectTabByLabelText(wait, "History")
|
|
74
|
+
"""
|
|
46
75
|
component = TabUtils.findTabByLabelText(wait, label)
|
|
47
|
-
|
|
76
|
+
ComponentUtils.click(wait, component)
|
|
48
77
|
|
|
49
78
|
@staticmethod
|
|
50
79
|
def checkTabSelectedByLabelText(wait: WebDriverWait, label: str):
|
|
80
|
+
"""
|
|
81
|
+
Check if a tab is currently selected (active).
|
|
82
|
+
|
|
83
|
+
Returns True if the tab has the "Selected Tab" indicator (aria-label or span text),
|
|
84
|
+
False otherwise. Useful in test assertions to verify navigation.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
wait: WebDriverWait instance.
|
|
88
|
+
label: Exact visible label text of the tab to check.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
bool: True if tab is selected, False otherwise (or if tab not found).
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
>>> if TabUtils.checkTabSelectedByLabelText(wait, "Details"):
|
|
95
|
+
... print("Details tab is active")
|
|
96
|
+
>>> assert TabUtils.checkTabSelectedByLabelText(wait, "History"), "History tab should be selected"
|
|
97
|
+
"""
|
|
51
98
|
component = TabUtils.findTabByLabelText(wait, label)
|
|
52
99
|
|
|
53
100
|
select_text = "Selected Tab."
|
|
@@ -6,38 +6,42 @@ from robo_appian.utils.ComponentUtils import ComponentUtils
|
|
|
6
6
|
|
|
7
7
|
class TableUtils:
|
|
8
8
|
"""
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
Interact with Appian grid/table components: read cells, click rows, find elements.
|
|
10
|
+
|
|
11
|
+
Query and interact with table rows and cells using column names as locators.
|
|
12
|
+
Automatically handles row/column indexing, finding cells by their position, and
|
|
13
|
+
interacting with components within cells (buttons, links, inputs).
|
|
14
|
+
|
|
15
|
+
Key concepts:
|
|
16
|
+
- Columns are identified by their header 'abbr' (abbreviation) attribute
|
|
17
|
+
- Rows are 0-based in public APIs (first row = 0)
|
|
18
|
+
- Rows are internally 1-indexed in Appian's data-dnd-name (conversion is automatic)
|
|
19
|
+
|
|
20
|
+
All methods follow the wait-first pattern: pass WebDriverWait as the first argument.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
>>> from robo_appian.components.TableUtils import TableUtils
|
|
24
|
+
>>> from selenium.webdriver.support.ui import WebDriverWait
|
|
25
|
+
|
|
26
|
+
# Find a table by column name and count rows
|
|
27
|
+
table = TableUtils.findTableByColumnName(wait, "Employee ID")
|
|
18
28
|
row_count = TableUtils.rowCount(table)
|
|
19
|
-
|
|
20
|
-
driver.quit()
|
|
29
|
+
print(f"Table has {row_count} rows")
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
# Find and click component in specific cell (row 0, column "Status")
|
|
32
|
+
TableUtils.selectRowFromTableByColumnNameAndRowNumber(wait, 0, "Employee ID")
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"""
|
|
27
|
-
Counts the number of rows in a table.
|
|
34
|
+
# Get a specific cell component
|
|
35
|
+
edit_button = TableUtils.findComponentFromTableCell(wait, 0, "Actions")
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Example:
|
|
32
|
-
row_count = TableUtils.rowCount(table)
|
|
33
|
-
"""
|
|
37
|
+
# Interact with element in table cell
|
|
38
|
+
component = TableUtils.findComponentByColumnNameAndRowNumber(wait, 1, "Status")
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return len(rows)
|
|
40
|
+
Note:
|
|
41
|
+
- Tables are located by column header 'abbr' attribute
|
|
42
|
+
- Column positions are derived from header class attributes (e.g., "headCell_2")
|
|
43
|
+
- Hidden/aria-hidden elements are automatically excluded
|
|
44
|
+
"""
|
|
41
45
|
|
|
42
46
|
@staticmethod
|
|
43
47
|
def __findColumNumberByColumnName(tableObject, columnName):
|
|
@@ -55,7 +59,9 @@ class TableUtils:
|
|
|
55
59
|
component = tableObject.find_element(By.XPATH, xpath)
|
|
56
60
|
|
|
57
61
|
if component is None:
|
|
58
|
-
raise ValueError(
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Could not find a column with abbr '{columnName}' in the table header."
|
|
64
|
+
)
|
|
59
65
|
|
|
60
66
|
class_string = component.get_attribute("class")
|
|
61
67
|
partial_string = "headCell_"
|
|
@@ -67,7 +73,9 @@ class TableUtils:
|
|
|
67
73
|
selected_word = word
|
|
68
74
|
|
|
69
75
|
if selected_word is None:
|
|
70
|
-
raise ValueError(
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Could not find a class containing '{partial_string}' in the column header for '{columnName}'."
|
|
78
|
+
)
|
|
71
79
|
|
|
72
80
|
data = selected_word.split("_")
|
|
73
81
|
return int(data[1])
|
|
@@ -97,17 +105,14 @@ class TableUtils:
|
|
|
97
105
|
rowNumber = rowNumber + 1
|
|
98
106
|
columnNumber = columnNumber + 1
|
|
99
107
|
xpath = f'.//table[./thead/tr/th[@abbr="{columnName}"]]/tbody/tr[@data-dnd-name="row {rowNumber}"]/td[not (@data-empty-grid-message)][{columnNumber}]/*'
|
|
100
|
-
|
|
101
|
-
component = wait.until(EC.element_to_be_clickable((By.XPATH, xpath)))
|
|
102
|
-
except Exception as e:
|
|
103
|
-
raise Exception(f"Could not find component in cell at row {rowNumber}, column '{columnName}': {e}")
|
|
108
|
+
component = wait.until(EC.element_to_be_clickable((By.XPATH, xpath)))
|
|
104
109
|
return component
|
|
105
110
|
|
|
106
111
|
@staticmethod
|
|
107
112
|
def selectRowFromTableByColumnNameAndRowNumber(wait, rowNumber, columnName):
|
|
108
113
|
row = TableUtils.__findRowByColumnNameAndRowNumber(wait, rowNumber, columnName)
|
|
109
114
|
row = wait.until(EC.element_to_be_clickable(row))
|
|
110
|
-
|
|
115
|
+
ComponentUtils.click(wait, row)
|
|
111
116
|
|
|
112
117
|
@staticmethod
|
|
113
118
|
def findComponentByColumnNameAndRowNumber(wait, rowNumber, columnName):
|
|
@@ -118,7 +123,9 @@ class TableUtils:
|
|
|
118
123
|
parts = id.rsplit("_", 1)
|
|
119
124
|
columnNumber = int(parts[-1])
|
|
120
125
|
|
|
121
|
-
tableRow = TableUtils.__findRowByColumnNameAndRowNumber(
|
|
126
|
+
tableRow = TableUtils.__findRowByColumnNameAndRowNumber(
|
|
127
|
+
wait, rowNumber, columnName
|
|
128
|
+
)
|
|
122
129
|
xpath = f"./td[{columnNumber + 1}]/*"
|
|
123
130
|
component = ComponentUtils.findChildComponentByXpath(wait, tableRow, xpath)
|
|
124
131
|
component = wait.until(EC.element_to_be_clickable(component))
|
|
@@ -137,13 +144,30 @@ class TableUtils:
|
|
|
137
144
|
"""
|
|
138
145
|
|
|
139
146
|
xpath = f'.//table[./thead/tr/th[@abbr="{columnName}"]]'
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
raise Exception(f"Could not find table with column name '{columnName}': {e}")
|
|
144
|
-
|
|
145
|
-
try:
|
|
146
|
-
component = wait.until(EC.element_to_be_clickable(component))
|
|
147
|
-
except Exception as e:
|
|
148
|
-
raise Exception(f"Table found by column name '{columnName}' is not clickable: {e}")
|
|
147
|
+
component = wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))
|
|
148
|
+
|
|
149
|
+
component = wait.until(EC.element_to_be_clickable(component))
|
|
149
150
|
return component
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def rowCount(tableObject):
|
|
154
|
+
"""
|
|
155
|
+
Count non-empty rows in a table.
|
|
156
|
+
|
|
157
|
+
Returns the number of data rows (excluding empty grid message placeholders).
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
tableObject: WebElement representing the table (from findTableByColumnName).
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
int: Number of rows in the table.
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
>>> table = TableUtils.findTableByColumnName(wait, "Name")
|
|
167
|
+
>>> rows = TableUtils.rowCount(table)
|
|
168
|
+
>>> print(f"Found {rows} employees")
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
xpath = "./tbody/tr[./td[not (@data-empty-grid-message)]]"
|
|
172
|
+
rows = tableObject.find_elements(By.XPATH, xpath)
|
|
173
|
+
return len(rows)
|
|
@@ -12,25 +12,70 @@ from robo_appian.components.SearchDropdownUtils import SearchDropdownUtils
|
|
|
12
12
|
|
|
13
13
|
class ComponentDriver:
|
|
14
14
|
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
High-level action router for Appian UI components: unifies all component interactions.
|
|
16
|
+
|
|
17
|
+
Instead of importing individual utilities (InputUtils, ButtonUtils, etc.), use ComponentDriver
|
|
18
|
+
to route actions by component type and label. This simplifies test code and centralizes
|
|
19
|
+
the mapping between component types and available actions.
|
|
20
|
+
|
|
21
|
+
Supported component types: Date, Input Text, Button, Drop Down, Search Input Text,
|
|
22
|
+
Search Drop Down, Label, Link, Tab.
|
|
23
|
+
|
|
24
|
+
All methods follow the wait-first pattern: pass WebDriverWait as the first argument.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> from robo_appian.controllers.ComponentDriver import ComponentDriver
|
|
28
|
+
>>> # Set a date value
|
|
29
|
+
>>> ComponentDriver.execute(wait, "Date", "Set Value", "Start Date", "01/01/2024")
|
|
30
|
+
>>> # Fill a text input
|
|
31
|
+
>>> ComponentDriver.execute(wait, "Input Text", "Set Value", "Username", "john_doe")
|
|
32
|
+
>>> # Click a button
|
|
33
|
+
>>> ComponentDriver.execute(wait, "Button", "Click", "Submit", None)
|
|
34
|
+
>>> # Select dropdown option
|
|
35
|
+
>>> ComponentDriver.execute(wait, "Drop Down", "Select", "Status", "Active")
|
|
20
36
|
"""
|
|
21
37
|
|
|
22
38
|
@staticmethod
|
|
23
39
|
def execute(wait: WebDriverWait, type, action, label, value):
|
|
24
40
|
"""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
Execute a high-level action on an Appian component by type, action, label, and value.
|
|
42
|
+
|
|
43
|
+
Routes the action to the appropriate utility class (InputUtils, ButtonUtils, etc.) based
|
|
44
|
+
on component type and action. Eliminates need for direct utility imports in tests.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
wait: WebDriverWait instance (required by all robo_appian utilities).
|
|
48
|
+
type: Component type string. Supported: "Date", "Input Text", "Button", "Drop Down",
|
|
49
|
+
"Search Input Text", "Search Drop Down", "Label", "Link", "Tab".
|
|
50
|
+
action: Action string. Common values: "Set Value", "Click", "Select", "Find".
|
|
51
|
+
Valid actions depend on component type (see supported combinations below).
|
|
52
|
+
label: Exact visible label text of the component on the page (used to locate element).
|
|
53
|
+
value: Value to set or select. None for click/find actions; required for Set Value/Select.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
None
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ValueError: If component type or action is not supported.
|
|
60
|
+
TimeoutException: If component not found or not clickable within wait timeout.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
>>> # Set date value
|
|
64
|
+
>>> ComponentDriver.execute(wait, "Date", "Set Value", "Start Date", "01/01/2024")
|
|
65
|
+
>>> # Fill text input
|
|
66
|
+
>>> ComponentDriver.execute(wait, "Input Text", "Set Value", "Email", "test@example.com")
|
|
67
|
+
>>> # Click button
|
|
68
|
+
>>> ComponentDriver.execute(wait, "Button", "Click", "Submit", None)
|
|
69
|
+
>>> # Select dropdown
|
|
70
|
+
>>> ComponentDriver.execute(wait, "Drop Down", "Select", "Department", "Sales")
|
|
71
|
+
>>> # Search and select from search dropdown
|
|
72
|
+
>>> ComponentDriver.execute(wait, "Search Drop Down", "Select", "Employee", "John Doe")
|
|
73
|
+
>>> # Click link
|
|
74
|
+
>>> ComponentDriver.execute(wait, "Link", "Click", "Learn More", None)
|
|
75
|
+
>>> # Check if label exists
|
|
76
|
+
>>> ComponentDriver.execute(wait, "Label", "Find", "Important Notice", None)
|
|
77
|
+
>>> # Select tab
|
|
78
|
+
>>> ComponentDriver.execute(wait, "Tab", "Find", "Details", None)
|
|
34
79
|
"""
|
|
35
80
|
# This method executes an action on a specified component type based on the provided parameters.
|
|
36
81
|
|
|
@@ -50,7 +95,9 @@ class ComponentDriver:
|
|
|
50
95
|
case "Search Input Text":
|
|
51
96
|
match action:
|
|
52
97
|
case "Select":
|
|
53
|
-
SearchInputUtils.selectSearchDropdownByLabelText(
|
|
98
|
+
SearchInputUtils.selectSearchDropdownByLabelText(
|
|
99
|
+
wait, label, value
|
|
100
|
+
)
|
|
54
101
|
case _:
|
|
55
102
|
raise ValueError(f"Unsupported action for {type}: {action}")
|
|
56
103
|
case "Label":
|
|
@@ -74,7 +121,9 @@ class ComponentDriver:
|
|
|
74
121
|
case "Search Drop Down":
|
|
75
122
|
match action:
|
|
76
123
|
case "Select":
|
|
77
|
-
SearchDropdownUtils.selectSearchDropdownValueByLabelText(
|
|
124
|
+
SearchDropdownUtils.selectSearchDropdownValueByLabelText(
|
|
125
|
+
wait, label, value
|
|
126
|
+
)
|
|
78
127
|
case _:
|
|
79
128
|
raise ValueError(f"Unsupported action for {type}: {action}")
|
|
80
129
|
case "Button":
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
class MyCustomError(Exception):
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
|
+
Custom exception for robo_appian-specific error conditions.
|
|
4
|
+
|
|
5
|
+
Use this exception when robo_appian operations encounter conditions that require
|
|
6
|
+
special handling distinct from standard Selenium or Python exceptions.
|
|
7
|
+
|
|
8
|
+
Examples:
|
|
9
|
+
>>> raise MyCustomError("Element not found in expected state")
|
|
10
|
+
>>> try:
|
|
11
|
+
... some_operation()
|
|
12
|
+
... except MyCustomError as e:
|
|
13
|
+
... print(f"Custom error occurred: {e.message}")
|
|
14
|
+
"""
|
|
3
15
|
|
|
4
16
|
def __init__(self, message="This is a custom error!"):
|
|
5
17
|
self.message = message
|