synapse-hub 2.4.0 → 2.4.2
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.
- package/.agents/agents/orchestrator.md +3 -0
- package/.agents/agents/pom-crafter.md +3 -0
- package/.agents/agents/quality-gatekeeper.md +1 -0
- package/.agents/agents/test-runner.md +1 -0
- package/.agents/rules/graphify.md +14 -14
- package/.agents/rules/mandatory-playwright-live.md +29 -29
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
- package/.agents/templates/completion_report.md +57 -57
- package/.agents/workflows/graphify.md +10 -10
- package/.cursor/rules/language.mdc +7 -7
- package/README.en.md +9 -3
- package/README.md +9 -3
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +21 -21
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +21 -21
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +21 -21
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +21 -21
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +21 -21
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +20 -20
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +20 -20
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +20 -20
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +20 -20
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +20 -20
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +22 -22
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +22 -22
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +22 -22
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +22 -22
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +22 -22
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +21 -21
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +21 -21
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +21 -21
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +21 -21
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +22 -22
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +21 -21
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +21 -21
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +21 -21
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +21 -21
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +21 -21
- package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +94 -69
- package/benchmarks/qa_eval/reports/eval_results.json +460 -104
- package/bin/agent-qa.js +6 -1
- package/docs/ARCHITECTURE.md +13 -2
- package/package.json +6 -6
- package/scripts/install.ps1 +112 -112
- package/scripts/install_global_plugin.ps1 +1 -1
- package/scripts/install_global_plugin.sh +1 -1
- package/scripts/lint_skills.ps1 +57 -57
- package/skills_manifest.json +1 -1
- package/synapse.config.json +1 -1
- package/tools/TOOLS_RUNBOOK.md +6 -1
- package/tools/__init__.py +1 -1
- package/tools/agent_canary.py +501 -501
- package/tools/api_fuzzer.py +11 -2
- package/tools/browser_helpers.js +2 -2
- package/tools/bump_version.py +8 -0
- package/tools/check_version_sync.py +18 -0
- package/tools/cli.py +210 -48
- package/tools/count_tokens.py +4 -4
- package/tools/dashboard_generator.py +10 -10
- package/tools/e2e_flow_prober.sh +0 -0
- package/tools/generate_allure_report.sh +0 -0
- package/tools/generate_test_reports.sh +0 -0
- package/tools/mcp_excel_server.py +3 -1
- package/tools/mcp_excel_server.sh +0 -0
- package/tools/mcp_server.py +1 -1
- package/tools/micro_mutator.py +4 -2
- package/tools/pinpoint_failure.py +88 -88
- package/tools/setup_project.py +1 -1
- package/tools/skillspector.py +31 -0
- package/tools/update_sentinel.py +3 -3
- package/tools/verify_all.py +16 -6
- package/tools/verify_all.sh +0 -0
- package/tools/visual_som_helper.py +14 -3
- package/tools/welcome_page.py +1 -1
- package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +0 -83
|
@@ -1,209 +1,209 @@
|
|
|
1
|
-
# Groovy Patterns for Katalon Studio
|
|
2
|
-
|
|
3
|
-
This document provides common Groovy patterns and best practices for Katalon Studio custom keywords and test scripts.
|
|
4
|
-
|
|
5
|
-
## Custom Keyword Structure
|
|
6
|
-
|
|
7
|
-
### Basic Custom Keyword Class
|
|
8
|
-
|
|
9
|
-
```groovy
|
|
10
|
-
package com.enterprise.keywords
|
|
11
|
-
|
|
12
|
-
import com.kms.katalon.core.annotation.Keyword
|
|
13
|
-
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
|
|
14
|
-
import internal.GlobalVariable
|
|
15
|
-
|
|
16
|
-
class LoginKeywords {
|
|
17
|
-
|
|
18
|
-
@Keyword
|
|
19
|
-
def login(String username, String password) {
|
|
20
|
-
WebUI.setText(findTestObject('Page_Login/input_Username'), username)
|
|
21
|
-
WebUI.setEncryptedText(findTestObject('Page_Login/input_Password'), password)
|
|
22
|
-
WebUI.click(findTestObject('Page_Login/btn_Login'))
|
|
23
|
-
WebUI.waitForPageLoad(GlobalVariable.timeout)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@Keyword
|
|
27
|
-
def loginWithGlobalVariables() {
|
|
28
|
-
login(GlobalVariable.username, GlobalVariable.password)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@Keyword
|
|
32
|
-
def verifyLoginSuccess() {
|
|
33
|
-
WebUI.verifyElementPresent(findTestObject('Page_Dashboard/header_Welcome'),
|
|
34
|
-
GlobalVariable.timeout)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Static Keywords
|
|
40
|
-
|
|
41
|
-
When keywords don't need instance state, make them static:
|
|
42
|
-
|
|
43
|
-
```groovy
|
|
44
|
-
package com.enterprise.utils
|
|
45
|
-
|
|
46
|
-
import com.kms.katalon.core.annotation.Keyword
|
|
47
|
-
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
|
|
48
|
-
|
|
49
|
-
class NavigationUtils {
|
|
50
|
-
|
|
51
|
-
@Keyword
|
|
52
|
-
static def navigateToPage(String pageName) {
|
|
53
|
-
String url = "${GlobalVariable.baseUrl}/${pageName}"
|
|
54
|
-
WebUI.navigateToUrl(url)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@Keyword
|
|
58
|
-
static def refreshUntilElementVisible(TestObject element, int maxAttempts = 5) {
|
|
59
|
-
int attempts = 0
|
|
60
|
-
while (attempts < maxAttempts) {
|
|
61
|
-
if (WebUI.verifyElementVisible(element, FailureHandling.OPTIONAL)) {
|
|
62
|
-
return true
|
|
63
|
-
}
|
|
64
|
-
WebUI.refresh()
|
|
65
|
-
attempts++
|
|
66
|
-
}
|
|
67
|
-
return false
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Common Patterns
|
|
73
|
-
|
|
74
|
-
### Wait and Retry Pattern
|
|
75
|
-
|
|
76
|
-
```groovy
|
|
77
|
-
@Keyword
|
|
78
|
-
def waitAndRetry(TestObject element, int maxAttempts = 3, int delaySeconds = 2) {
|
|
79
|
-
for (int i = 0; i < maxAttempts; i++) {
|
|
80
|
-
try {
|
|
81
|
-
WebUI.waitForElementPresent(element, delaySeconds)
|
|
82
|
-
return true
|
|
83
|
-
} catch (Exception e) {
|
|
84
|
-
if (i == maxAttempts - 1) {
|
|
85
|
-
throw e
|
|
86
|
-
}
|
|
87
|
-
WebUI.delay(delaySeconds)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return false
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Conditional Element Interaction
|
|
95
|
-
|
|
96
|
-
```groovy
|
|
97
|
-
@Keyword
|
|
98
|
-
def clickIfPresent(TestObject element, int timeout = 5) {
|
|
99
|
-
if (WebUI.verifyElementPresent(element, timeout, FailureHandling.OPTIONAL)) {
|
|
100
|
-
WebUI.click(element)
|
|
101
|
-
return true
|
|
102
|
-
}
|
|
103
|
-
return false
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@Keyword
|
|
107
|
-
def closePopupIfExists(TestObject closeButton) {
|
|
108
|
-
if (WebUI.verifyElementVisible(closeButton, FailureHandling.OPTIONAL)) {
|
|
109
|
-
WebUI.click(closeButton)
|
|
110
|
-
WebUI.delay(1)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Dynamic Object Creation
|
|
116
|
-
|
|
117
|
-
```groovy
|
|
118
|
-
@Keyword
|
|
119
|
-
def clickDynamicElement(String xpath) {
|
|
120
|
-
TestObject dynamicObject = new TestObject()
|
|
121
|
-
dynamicObject.addProperty('xpath', ConditionType.EQUALS, xpath)
|
|
122
|
-
WebUI.click(dynamicObject)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
@Keyword
|
|
126
|
-
def findElementByText(String tagName, String text) {
|
|
127
|
-
String xpath = "//${tagName}[contains(text(), '${text}')]"
|
|
128
|
-
TestObject element = new TestObject()
|
|
129
|
-
element.addProperty('xpath', ConditionType.EQUALS, xpath)
|
|
130
|
-
return element
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Data-Driven Helpers
|
|
135
|
-
|
|
136
|
-
```groovy
|
|
137
|
-
@Keyword
|
|
138
|
-
def executeWithTestData(String testDataName, Closure action) {
|
|
139
|
-
TestData data = findTestData(testDataName)
|
|
140
|
-
int rowCount = data.getRowNumbers()
|
|
141
|
-
|
|
142
|
-
for (int i = 1; i <= rowCount; i++) {
|
|
143
|
-
def row = [:]
|
|
144
|
-
data.getColumnNames().each { col ->
|
|
145
|
-
row[col] = data.getValue(col, i)
|
|
146
|
-
}
|
|
147
|
-
action(row)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Page Object Pattern
|
|
153
|
-
|
|
154
|
-
```groovy
|
|
155
|
-
package com.enterprise.pages
|
|
156
|
-
|
|
157
|
-
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
|
|
158
|
-
import internal.GlobalVariable
|
|
159
|
-
|
|
160
|
-
class LoginPage {
|
|
161
|
-
|
|
162
|
-
def open() {
|
|
163
|
-
WebUI.navigateToUrl(GlobalVariable.loginUrl)
|
|
164
|
-
return this
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
def login(String username, String password) {
|
|
168
|
-
// Implementation
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Error Handling and Logging
|
|
174
|
-
|
|
175
|
-
```groovy
|
|
176
|
-
import com.kms.katalon.core.util.KeywordUtil
|
|
177
|
-
|
|
178
|
-
@Keyword
|
|
179
|
-
def performActionWithErrorHandling(Closure action) {
|
|
180
|
-
try {
|
|
181
|
-
action()
|
|
182
|
-
KeywordUtil.markPassed("Action completed successfully")
|
|
183
|
-
} catch (Exception e) {
|
|
184
|
-
KeywordUtil.logInfo("Error occurred: ${e.message}")
|
|
185
|
-
WebUI.takeScreenshot()
|
|
186
|
-
KeywordUtil.markFailed("Action failed: ${e.message}")
|
|
187
|
-
throw e
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
@Keyword
|
|
192
|
-
def safeClick(TestObject element, String elementName = "Element") {
|
|
193
|
-
try {
|
|
194
|
-
WebUI.waitForElementClickable(element, 10)
|
|
195
|
-
WebUI.click(element)
|
|
196
|
-
KeywordUtil.logInfo("Clicked on ${elementName}")
|
|
197
|
-
} catch (Exception e) {
|
|
198
|
-
KeywordUtil.markWarning("Could not click ${elementName}: ${e.message}")
|
|
199
|
-
WebUI.takeScreenshot()
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### Best Practices
|
|
205
|
-
|
|
206
|
-
1. **Use Descriptive Names**: Use clear, readable keyword names.
|
|
207
|
-
2. **Handle Timeouts Gracefully**: Always set explicit wait timeouts rather than hard sleeps.
|
|
208
|
-
3. **Parameterize Magic Numbers**: Define constants for retries and timeouts.
|
|
209
|
-
4. **Failure Handling**: Use `FailureHandling.STOP_ON_FAILURE` for critical steps, `OPTIONAL` for transient dialogs.
|
|
1
|
+
# Groovy Patterns for Katalon Studio
|
|
2
|
+
|
|
3
|
+
This document provides common Groovy patterns and best practices for Katalon Studio custom keywords and test scripts.
|
|
4
|
+
|
|
5
|
+
## Custom Keyword Structure
|
|
6
|
+
|
|
7
|
+
### Basic Custom Keyword Class
|
|
8
|
+
|
|
9
|
+
```groovy
|
|
10
|
+
package com.enterprise.keywords
|
|
11
|
+
|
|
12
|
+
import com.kms.katalon.core.annotation.Keyword
|
|
13
|
+
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
|
|
14
|
+
import internal.GlobalVariable
|
|
15
|
+
|
|
16
|
+
class LoginKeywords {
|
|
17
|
+
|
|
18
|
+
@Keyword
|
|
19
|
+
def login(String username, String password) {
|
|
20
|
+
WebUI.setText(findTestObject('Page_Login/input_Username'), username)
|
|
21
|
+
WebUI.setEncryptedText(findTestObject('Page_Login/input_Password'), password)
|
|
22
|
+
WebUI.click(findTestObject('Page_Login/btn_Login'))
|
|
23
|
+
WebUI.waitForPageLoad(GlobalVariable.timeout)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Keyword
|
|
27
|
+
def loginWithGlobalVariables() {
|
|
28
|
+
login(GlobalVariable.username, GlobalVariable.password)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Keyword
|
|
32
|
+
def verifyLoginSuccess() {
|
|
33
|
+
WebUI.verifyElementPresent(findTestObject('Page_Dashboard/header_Welcome'),
|
|
34
|
+
GlobalVariable.timeout)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Static Keywords
|
|
40
|
+
|
|
41
|
+
When keywords don't need instance state, make them static:
|
|
42
|
+
|
|
43
|
+
```groovy
|
|
44
|
+
package com.enterprise.utils
|
|
45
|
+
|
|
46
|
+
import com.kms.katalon.core.annotation.Keyword
|
|
47
|
+
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
|
|
48
|
+
|
|
49
|
+
class NavigationUtils {
|
|
50
|
+
|
|
51
|
+
@Keyword
|
|
52
|
+
static def navigateToPage(String pageName) {
|
|
53
|
+
String url = "${GlobalVariable.baseUrl}/${pageName}"
|
|
54
|
+
WebUI.navigateToUrl(url)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Keyword
|
|
58
|
+
static def refreshUntilElementVisible(TestObject element, int maxAttempts = 5) {
|
|
59
|
+
int attempts = 0
|
|
60
|
+
while (attempts < maxAttempts) {
|
|
61
|
+
if (WebUI.verifyElementVisible(element, FailureHandling.OPTIONAL)) {
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
WebUI.refresh()
|
|
65
|
+
attempts++
|
|
66
|
+
}
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Common Patterns
|
|
73
|
+
|
|
74
|
+
### Wait and Retry Pattern
|
|
75
|
+
|
|
76
|
+
```groovy
|
|
77
|
+
@Keyword
|
|
78
|
+
def waitAndRetry(TestObject element, int maxAttempts = 3, int delaySeconds = 2) {
|
|
79
|
+
for (int i = 0; i < maxAttempts; i++) {
|
|
80
|
+
try {
|
|
81
|
+
WebUI.waitForElementPresent(element, delaySeconds)
|
|
82
|
+
return true
|
|
83
|
+
} catch (Exception e) {
|
|
84
|
+
if (i == maxAttempts - 1) {
|
|
85
|
+
throw e
|
|
86
|
+
}
|
|
87
|
+
WebUI.delay(delaySeconds)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Conditional Element Interaction
|
|
95
|
+
|
|
96
|
+
```groovy
|
|
97
|
+
@Keyword
|
|
98
|
+
def clickIfPresent(TestObject element, int timeout = 5) {
|
|
99
|
+
if (WebUI.verifyElementPresent(element, timeout, FailureHandling.OPTIONAL)) {
|
|
100
|
+
WebUI.click(element)
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Keyword
|
|
107
|
+
def closePopupIfExists(TestObject closeButton) {
|
|
108
|
+
if (WebUI.verifyElementVisible(closeButton, FailureHandling.OPTIONAL)) {
|
|
109
|
+
WebUI.click(closeButton)
|
|
110
|
+
WebUI.delay(1)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Dynamic Object Creation
|
|
116
|
+
|
|
117
|
+
```groovy
|
|
118
|
+
@Keyword
|
|
119
|
+
def clickDynamicElement(String xpath) {
|
|
120
|
+
TestObject dynamicObject = new TestObject()
|
|
121
|
+
dynamicObject.addProperty('xpath', ConditionType.EQUALS, xpath)
|
|
122
|
+
WebUI.click(dynamicObject)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Keyword
|
|
126
|
+
def findElementByText(String tagName, String text) {
|
|
127
|
+
String xpath = "//${tagName}[contains(text(), '${text}')]"
|
|
128
|
+
TestObject element = new TestObject()
|
|
129
|
+
element.addProperty('xpath', ConditionType.EQUALS, xpath)
|
|
130
|
+
return element
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Data-Driven Helpers
|
|
135
|
+
|
|
136
|
+
```groovy
|
|
137
|
+
@Keyword
|
|
138
|
+
def executeWithTestData(String testDataName, Closure action) {
|
|
139
|
+
TestData data = findTestData(testDataName)
|
|
140
|
+
int rowCount = data.getRowNumbers()
|
|
141
|
+
|
|
142
|
+
for (int i = 1; i <= rowCount; i++) {
|
|
143
|
+
def row = [:]
|
|
144
|
+
data.getColumnNames().each { col ->
|
|
145
|
+
row[col] = data.getValue(col, i)
|
|
146
|
+
}
|
|
147
|
+
action(row)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Page Object Pattern
|
|
153
|
+
|
|
154
|
+
```groovy
|
|
155
|
+
package com.enterprise.pages
|
|
156
|
+
|
|
157
|
+
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
|
|
158
|
+
import internal.GlobalVariable
|
|
159
|
+
|
|
160
|
+
class LoginPage {
|
|
161
|
+
|
|
162
|
+
def open() {
|
|
163
|
+
WebUI.navigateToUrl(GlobalVariable.loginUrl)
|
|
164
|
+
return this
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def login(String username, String password) {
|
|
168
|
+
// Implementation
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Error Handling and Logging
|
|
174
|
+
|
|
175
|
+
```groovy
|
|
176
|
+
import com.kms.katalon.core.util.KeywordUtil
|
|
177
|
+
|
|
178
|
+
@Keyword
|
|
179
|
+
def performActionWithErrorHandling(Closure action) {
|
|
180
|
+
try {
|
|
181
|
+
action()
|
|
182
|
+
KeywordUtil.markPassed("Action completed successfully")
|
|
183
|
+
} catch (Exception e) {
|
|
184
|
+
KeywordUtil.logInfo("Error occurred: ${e.message}")
|
|
185
|
+
WebUI.takeScreenshot()
|
|
186
|
+
KeywordUtil.markFailed("Action failed: ${e.message}")
|
|
187
|
+
throw e
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Keyword
|
|
192
|
+
def safeClick(TestObject element, String elementName = "Element") {
|
|
193
|
+
try {
|
|
194
|
+
WebUI.waitForElementClickable(element, 10)
|
|
195
|
+
WebUI.click(element)
|
|
196
|
+
KeywordUtil.logInfo("Clicked on ${elementName}")
|
|
197
|
+
} catch (Exception e) {
|
|
198
|
+
KeywordUtil.markWarning("Could not click ${elementName}: ${e.message}")
|
|
199
|
+
WebUI.takeScreenshot()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Best Practices
|
|
205
|
+
|
|
206
|
+
1. **Use Descriptive Names**: Use clear, readable keyword names.
|
|
207
|
+
2. **Handle Timeouts Gracefully**: Always set explicit wait timeouts rather than hard sleeps.
|
|
208
|
+
3. **Parameterize Magic Numbers**: Define constants for retries and timeouts.
|
|
209
|
+
4. **Failure Handling**: Use `FailureHandling.STOP_ON_FAILURE` for critical steps, `OPTIONAL` for transient dialogs.
|