synapse-hub 0.0.0-stage → 2.0.1

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 (122) hide show
  1. package/.agents/agents/failure-analyst.md +47 -0
  2. package/.agents/agents/live-explorer.md +41 -0
  3. package/.agents/agents/orchestrator.md +43 -0
  4. package/.agents/agents/pom-crafter.md +81 -0
  5. package/.agents/agents/quality-gatekeeper.md +47 -0
  6. package/.agents/agents/test-runner.md +43 -0
  7. package/.agents/rules/context-engineering.md +88 -0
  8. package/.agents/rules/git-push-protocol.md +101 -0
  9. package/.agents/rules/graphify.md +14 -0
  10. package/.agents/rules/lessons-learned.md +23 -0
  11. package/.agents/rules/mandatory-playwright-live.md +29 -0
  12. package/.agents/rules/skill-precedence.md +42 -0
  13. package/.agents/rules/systematic-debugging.md +99 -0
  14. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
  15. package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
  16. package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
  17. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
  18. package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
  19. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
  20. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
  21. package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
  22. package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
  23. package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
  24. package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
  25. package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
  26. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
  27. package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
  28. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
  29. package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
  30. package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
  31. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
  32. package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
  33. package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
  34. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
  35. package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
  36. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
  37. package/.agents/skills/github-actions-templates/SKILL.md +334 -0
  38. package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
  39. package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
  40. package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
  41. package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
  42. package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
  43. package/.agents/skills/lam-script-generator/SKILL.md +83 -0
  44. package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
  45. package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
  46. package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
  47. package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
  48. package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
  49. package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
  50. package/.agents/skills/self-healing-locator/SKILL.md +73 -0
  51. package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
  52. package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
  53. package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
  54. package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
  55. package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
  56. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
  57. package/.agents/templates/completion_report.md +57 -0
  58. package/.agents/workflows/graphify.md +10 -0
  59. package/.agents/workflows/test-impact.md +15 -0
  60. package/.agents/workflows/verify.md +34 -0
  61. package/AGENTS.md +115 -0
  62. package/README.md +537 -2
  63. package/bin/agent-qa.js +90 -0
  64. package/package.json +59 -4
  65. package/skills_manifest.json +262 -0
  66. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
  67. package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
  68. package/tools/TOOLS_RUNBOOK.md +298 -0
  69. package/tools/agent_canary.py +501 -0
  70. package/tools/analyze_project.py +242 -0
  71. package/tools/api_drift_sentinel.py +208 -0
  72. package/tools/auto_heal.py +189 -0
  73. package/tools/browser_helpers.js +593 -0
  74. package/tools/bug_reporter.py +224 -0
  75. package/tools/cache_resolver.py +846 -0
  76. package/tools/check_env.py +288 -0
  77. package/tools/ci_healing_hook.py +128 -0
  78. package/tools/cli.py +402 -0
  79. package/tools/config_decoupler.py +253 -0
  80. package/tools/db_sandbox.py +341 -0
  81. package/tools/distill_log.py +152 -0
  82. package/tools/e2e_flow_prober.ps1 +65 -0
  83. package/tools/flaky_detector.py +222 -0
  84. package/tools/fuzz_payloads.py +197 -0
  85. package/tools/generate_allure_report.ps1 +30 -0
  86. package/tools/generate_api_matrix.py +193 -0
  87. package/tools/generate_tc_from_story.py +182 -0
  88. package/tools/generate_test_reports.ps1 +135 -0
  89. package/tools/git_pre_commit_guard.py +203 -0
  90. package/tools/heal_pr_automator.py +195 -0
  91. package/tools/heuristic_locator_matcher.py +202 -0
  92. package/tools/infer_linter.py +238 -0
  93. package/tools/lint_ai_docs.py +254 -0
  94. package/tools/lookup_tc.py +165 -0
  95. package/tools/lookup_xpath.py +167 -0
  96. package/tools/matrix_sharder.py +146 -0
  97. package/tools/mcp_excel_server.ps1 +251 -0
  98. package/tools/mcp_server.py +490 -0
  99. package/tools/micro_mutator.py +309 -0
  100. package/tools/network_mock_shield.py +170 -0
  101. package/tools/omni_emitter.py +500 -0
  102. package/tools/omni_parser.py +435 -0
  103. package/tools/omni_transpiler.py +168 -0
  104. package/tools/pinpoint_failure.py +88 -0
  105. package/tools/project_state.py +169 -0
  106. package/tools/repo_map.json +12 -0
  107. package/tools/repo_map.py +277 -0
  108. package/tools/repo_map.txt +7 -0
  109. package/tools/safety_firewall.py +245 -0
  110. package/tools/setup_project.py +265 -0
  111. package/tools/smart_visual_masker.py +134 -0
  112. package/tools/test_impact.py +321 -0
  113. package/tools/tms_sync_bridge.py +342 -0
  114. package/tools/trace_data_flow.py +240 -0
  115. package/tools/traffic_session_converter.py +256 -0
  116. package/tools/universal_ir.py +480 -0
  117. package/tools/validate_groovy_scripts.py +173 -0
  118. package/tools/verify_all.py +496 -0
  119. package/tools/verify_all.sh +4 -0
  120. package/tools/visual_diff_canvas.py +142 -0
  121. package/tools/visual_som_helper.py +425 -0
  122. package/tools/wcag_audit.py +214 -0
@@ -0,0 +1,500 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Omni-Emitter: Universal Polyglot Code Generator & Transpiler
4
+ ------------------------------------------------------------
5
+ Generates 100% production-ready, idiomatically compliant automation code
6
+ from the Universal Intermediate Representation (universal_ir.ProjectIR).
7
+ Supports:
8
+ 1. Playwright TypeScript (playwright-ts)
9
+ 2. Playwright Python (playwright-python)
10
+ 3. Selenium Java (selenium-java)
11
+ 4. Katalon Groovy (katalon-groovy) - Clean POM Flat Architecture
12
+ 5. Cypress JavaScript (cypress-js)
13
+ """
14
+
15
+ import os
16
+ from typing import Dict, List, Optional
17
+
18
+ try:
19
+ from tools.universal_ir import (
20
+ ProjectIR, PageClassIR, TestCaseIR, MethodIR, StepIR, LocatorIR,
21
+ ActionType, LocatorStrategy, AssertionType
22
+ )
23
+ except ImportError:
24
+ from universal_ir import (
25
+ ProjectIR, PageClassIR, TestCaseIR, MethodIR, StepIR, LocatorIR,
26
+ ActionType, LocatorStrategy, AssertionType
27
+ )
28
+
29
+
30
+ def escape_js(s: str) -> str:
31
+ """Escapes string for single-quoted JS/TS literals."""
32
+ return s.replace("\\", "\\\\").replace("'", "\\'")
33
+
34
+
35
+ def escape_java(s: str) -> str:
36
+ """Escapes string for double-quoted Java/Groovy literals."""
37
+ return s.replace("\\", "\\\\").replace('"', '\\"')
38
+
39
+
40
+ class OmniEmitter:
41
+ """Universal Code Emitter for Multi-Language Automation Frameworks."""
42
+
43
+ SUPPORTED_TARGETS = [
44
+ "playwright-ts",
45
+ "playwright-python",
46
+ "selenium-java",
47
+ "katalon-groovy",
48
+ "cypress-js"
49
+ ]
50
+
51
+ @classmethod
52
+ def emit(cls, project: ProjectIR, target_framework: str) -> Dict[str, str]:
53
+ target = target_framework.lower().strip()
54
+ if target not in cls.SUPPORTED_TARGETS:
55
+ raise ValueError(f"Unsupported target framework '{target_framework}'. Supported: {cls.SUPPORTED_TARGETS}")
56
+
57
+ output_files: Dict[str, str] = {}
58
+
59
+ if target == "playwright-ts":
60
+ output_files.update(cls._emit_playwright_ts(project))
61
+ elif target == "playwright-python":
62
+ output_files.update(cls._emit_playwright_python(project))
63
+ elif target == "selenium-java":
64
+ output_files.update(cls._emit_selenium_java(project))
65
+ elif target == "katalon-groovy":
66
+ output_files.update(cls._emit_katalon_groovy(project))
67
+ elif target == "cypress-js":
68
+ output_files.update(cls._emit_cypress_js(project))
69
+
70
+ return output_files
71
+
72
+ # -------------------------------------------------------------------------
73
+ # 1. Playwright TypeScript Emitter
74
+ # -------------------------------------------------------------------------
75
+ @classmethod
76
+ def _emit_safe_interact_helper(cls) -> str:
77
+ return """import { Page, Locator } from "@playwright/test";
78
+
79
+ export interface SafeInteractOptions {
80
+ action?: 'click' | 'fill' | 'hover' | 'select' | 'upload' | 'check';
81
+ value?: string;
82
+ files?: string | string[];
83
+ fallbackToJsDispatch?: boolean;
84
+ ensureVisible?: boolean;
85
+ retryCount?: number;
86
+ stabilityTimeoutMs?: number;
87
+ }
88
+
89
+ /**
90
+ * Resilient Cross-Engine Interaction Polyfill
91
+ * Neutralizes layout rendering & event dispatch differences across Chromium, WebKit, and Gecko:
92
+ * 1. Scrolls target into view if needed (ensureVisible)
93
+ * 2. Catches WebKit/Safari overlay interception and gracefully fallbacks to JS dispatchEvent
94
+ * 3. Handles resilient retries during debounce or active animation
95
+ */
96
+ export async function safeInteract(
97
+ page: Page,
98
+ target: Locator | string,
99
+ options: SafeInteractOptions = {}
100
+ ): Promise<void> {
101
+ const {
102
+ action = 'click',
103
+ value = '',
104
+ files,
105
+ fallbackToJsDispatch = true,
106
+ ensureVisible = true,
107
+ retryCount = 2,
108
+ stabilityTimeoutMs = 5000
109
+ } = options;
110
+
111
+ const locator = typeof target === 'string' ? page.locator(target) : target;
112
+
113
+ for (let attempt = 0; attempt <= retryCount; attempt++) {
114
+ try {
115
+ if (ensureVisible) {
116
+ await locator.scrollIntoViewIfNeeded({ timeout: stabilityTimeoutMs }).catch(() => {});
117
+ }
118
+
119
+ if (action === 'click') {
120
+ await locator.click({ timeout: stabilityTimeoutMs });
121
+ } else if (action === 'fill') {
122
+ await locator.fill(value, { timeout: stabilityTimeoutMs });
123
+ } else if (action === 'check') {
124
+ await locator.check({ timeout: stabilityTimeoutMs });
125
+ } else if (action === 'upload' && files) {
126
+ await locator.setInputFiles(files);
127
+ } else if (action === 'hover') {
128
+ await locator.hover({ timeout: stabilityTimeoutMs });
129
+ }
130
+ return;
131
+ } catch (err: any) {
132
+ const isLastAttempt = attempt === retryCount;
133
+ const isIntercepted = err.message && (
134
+ err.message.includes('intercepted') ||
135
+ err.message.includes('another element') ||
136
+ err.message.includes('not visible') ||
137
+ err.message.includes('pointer-events')
138
+ );
139
+
140
+ if (fallbackToJsDispatch && isIntercepted) {
141
+ await locator.evaluate((el: any) => {
142
+ el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
143
+ }).catch(() => {});
144
+ return;
145
+ }
146
+
147
+ if (isLastAttempt) {
148
+ throw err;
149
+ }
150
+ await page.waitForTimeout(200);
151
+ }
152
+ }
153
+ }
154
+ """
155
+
156
+ @classmethod
157
+ def _emit_playwright_ts(cls, project: ProjectIR) -> Dict[str, str]:
158
+ files: Dict[str, str] = {}
159
+ files["helpers/safeInteract.ts"] = cls._emit_safe_interact_helper()
160
+
161
+ for page in project.pages:
162
+ code = [
163
+ 'import { Page, Locator, expect } from "@playwright/test";',
164
+ 'import { safeInteract } from "../helpers/safeInteract";',
165
+ "",
166
+ f"export class {page.name} {{",
167
+ " readonly page: Page;",
168
+ ]
169
+
170
+ for loc in page.locators:
171
+ code.append(f" readonly {loc.name}: Locator;")
172
+
173
+ code.extend([
174
+ "",
175
+ " constructor(page: Page) {",
176
+ " this.page = page;",
177
+ ])
178
+
179
+ for loc in page.locators:
180
+ if loc.strategy == LocatorStrategy.ROLE:
181
+ role_parts = loc.value.split("[name='")
182
+ role_name = escape_js(role_parts[0])
183
+ acc_name = escape_js(role_parts[1].rstrip("']") if len(role_parts) > 1 else "")
184
+ code.append(f" this.{loc.name} = page.getByRole('{role_name}' as any, {{ name: '{acc_name}' }});")
185
+ elif loc.strategy == LocatorStrategy.TEST_ID:
186
+ code.append(f" this.{loc.name} = page.getByTestId('{escape_js(loc.value)}');")
187
+ elif loc.strategy == LocatorStrategy.LABEL:
188
+ code.append(f" this.{loc.name} = page.getByLabel('{escape_js(loc.value)}');")
189
+ elif loc.strategy == LocatorStrategy.PLACEHOLDER:
190
+ code.append(f" this.{loc.name} = page.getByPlaceholder('{escape_js(loc.value)}');")
191
+ elif loc.strategy == LocatorStrategy.TEXT:
192
+ code.append(f" this.{loc.name} = page.getByText('{escape_js(loc.value)}');")
193
+ else:
194
+ code.append(f" this.{loc.name} = page.locator('{escape_js(loc.value)}');")
195
+
196
+ code.append(" }")
197
+
198
+ for m in page.methods:
199
+ params_decl = ", ".join([f"{p}: string" for p in m.parameters])
200
+ code.extend([
201
+ "",
202
+ f" async {m.name}({params_decl}): Promise<void> {{",
203
+ ])
204
+ if not m.steps:
205
+ code.append(" // Auto-generated method stub")
206
+ for s in m.steps:
207
+ if s.action == ActionType.GOTO:
208
+ code.append(f" await this.page.goto('{escape_js(s.value or '')}');")
209
+ elif s.action == ActionType.CLICK:
210
+ target = s.target or "element"
211
+ code.append(f" await safeInteract(this.page, this.{target}, {{ action: 'click', fallbackToJsDispatch: true, ensureVisible: true }});")
212
+ elif s.action == ActionType.FILL:
213
+ target = s.target or "input"
214
+ val = s.value or "value"
215
+ code.append(f" await expect(this.{target}).toBeEditable();")
216
+ code.append(f" await safeInteract(this.page, this.{target}, {{ action: 'fill', value: '{escape_js(val)}' }});")
217
+ elif s.action == ActionType.WAIT:
218
+ if s.is_sanitized_wait:
219
+ if s.target:
220
+ code.append(f" await this.page.evaluate((sel) => (window as any).__qa?.probeStability(sel), '{escape_js(s.target)}'); // Event-Driven Stability Probe")
221
+ else:
222
+ code.append(" await this.page.waitForLoadState('networkidle'); // Event-Driven Network Idle")
223
+ else:
224
+ code.append(f" await this.page.waitForTimeout({s.timeout_ms});")
225
+ elif s.action == ActionType.ASSERT:
226
+ code.append(" await expect(this.page).toBeDefined();")
227
+ else:
228
+ code.append(f" // {s.description}")
229
+ code.append(" }")
230
+
231
+ code.append("}\n")
232
+ files[f"pages/{page.name}.ts"] = "\n".join(code)
233
+
234
+ if project.test_cases:
235
+ t_code = [
236
+ 'import { test, expect } from "@playwright/test";',
237
+ 'import { safeInteract } from "../helpers/safeInteract";',
238
+ ]
239
+ for page in project.pages:
240
+ t_code.append(f'import {{ {page.name} }} from "./pages/{page.name}";')
241
+ t_code.append("")
242
+
243
+ for tc in project.test_cases:
244
+ t_code.extend([
245
+ f"test('{escape_js(tc.id)}: {escape_js(tc.title)}', async ({{ page }}) => {{",
246
+ f" const uniqueCode = 'AUTO_' + Date.now();",
247
+ ])
248
+
249
+ # Emit Hermetic Pre-flight Fixtures if present
250
+ if tc.hermetic_fixtures:
251
+ t_code.append(" // --- [Hermetic Test Pre-flight Fixture Setup] ---")
252
+ for hf in tc.hermetic_fixtures:
253
+ t_code.append(f" // Fixture: {hf.variable_name} ({hf.fixture_type})")
254
+ if hf.code_snippet:
255
+ for c_line in hf.code_snippet.splitlines():
256
+ t_code.append(f" {c_line}")
257
+ t_code.append("")
258
+
259
+ for page in project.pages:
260
+ var_name = page.name[0].lower() + page.name[1:]
261
+ t_code.append(f" const {var_name} = new {page.name}(page);")
262
+
263
+ for s in tc.steps:
264
+ if s.action == ActionType.GOTO:
265
+ t_code.append(f" await page.goto('{escape_js(s.value or '')}');")
266
+ elif s.action == ActionType.CLICK:
267
+ t_code.append(f" await safeInteract(page, '{escape_js(s.target or '')}', {{ action: 'click', fallbackToJsDispatch: true, ensureVisible: true }});")
268
+ elif s.action == ActionType.FILL:
269
+ t_code.append(f" await expect(page.locator('{escape_js(s.target or '')}')).toBeEditable();")
270
+ t_code.append(f" await safeInteract(page, '{escape_js(s.target or '')}', {{ action: 'fill', value: '{escape_js(s.value or '')}' }});")
271
+ elif s.action == ActionType.WAIT:
272
+ if s.is_sanitized_wait:
273
+ if s.target:
274
+ t_code.append(f" await page.evaluate((sel) => (window as any).__qa?.probeStability(sel), '{escape_js(s.target)}'); // Event-Driven Stability Probe")
275
+ else:
276
+ t_code.append(" await page.waitForLoadState('networkidle'); // Event-Driven Network Idle")
277
+ else:
278
+ t_code.append(f" await page.waitForTimeout({s.timeout_ms});")
279
+ elif s.action == ActionType.API_CALL:
280
+ t_code.append(f" await page.request.post('{escape_js(s.target or '')}', {{ data: {s.value or '{}'} }}); // {s.description}")
281
+ elif s.action == ActionType.ASSERT:
282
+ t_code.append(" await expect(page).toHaveURL(/.*\\/.*/);")
283
+ else:
284
+ t_code.append(f" // {s.description}")
285
+
286
+ t_code.append("});\n")
287
+ files[f"tests/{project.name}.spec.ts"] = "\n".join(t_code)
288
+
289
+ return files
290
+
291
+ # -------------------------------------------------------------------------
292
+ # 2. Playwright Python Emitter
293
+ # -------------------------------------------------------------------------
294
+ @classmethod
295
+ def _emit_playwright_python(cls, project: ProjectIR) -> Dict[str, str]:
296
+ files: Dict[str, str] = {}
297
+
298
+ for page in project.pages:
299
+ code = [
300
+ "from playwright.sync_api import Page, expect",
301
+ "",
302
+ f"class {page.name}:",
303
+ " def __init__(self, page: Page):",
304
+ " self.page = page",
305
+ ]
306
+ for loc in page.locators:
307
+ if loc.strategy == LocatorStrategy.ROLE:
308
+ role_parts = loc.value.split("[name='")
309
+ r_name = escape_java(role_parts[0])
310
+ acc = escape_java(role_parts[1].rstrip("']") if len(role_parts) > 1 else "")
311
+ code.append(f' self.{loc.name} = page.get_by_role("{r_name}", name="{acc}")')
312
+ elif loc.strategy == LocatorStrategy.TEST_ID:
313
+ code.append(f' self.{loc.name} = page.get_by_test_id("{escape_java(loc.value)}")')
314
+ else:
315
+ code.append(f' self.{loc.name} = page.locator("{escape_java(loc.value)}")')
316
+
317
+ for m in page.methods:
318
+ params_decl = ", ".join(["self"] + m.parameters)
319
+ code.extend([
320
+ "",
321
+ f" def {m.name}({params_decl}):",
322
+ ])
323
+ if not m.steps:
324
+ code.append(" pass")
325
+ for s in m.steps:
326
+ if s.action == ActionType.CLICK:
327
+ code.append(f" self.{s.target}.click()")
328
+ elif s.action == ActionType.FILL:
329
+ code.append(f' self.{s.target}.fill("{escape_java(s.value or "")}")')
330
+ else:
331
+ code.append(f" # {s.description}")
332
+
333
+ code.append("\n")
334
+ files[f"pages/{page.name}.py"] = "\n".join(code)
335
+
336
+ return files
337
+
338
+ # -------------------------------------------------------------------------
339
+ # 3. Selenium Java Emitter (Clean POM)
340
+ # -------------------------------------------------------------------------
341
+ @classmethod
342
+ def _emit_selenium_java(cls, project: ProjectIR) -> Dict[str, str]:
343
+ files: Dict[str, str] = {}
344
+
345
+ for page in project.pages:
346
+ code = [
347
+ "package pages;",
348
+ "",
349
+ "import org.openqa.selenium.By;",
350
+ "import org.openqa.selenium.WebDriver;",
351
+ "import org.openqa.selenium.support.ui.WebDriverWait;",
352
+ "import org.openqa.selenium.support.ui.ExpectedConditions;",
353
+ "import java.time.Duration;",
354
+ "",
355
+ f"public class {page.name} {{",
356
+ " private final WebDriver driver;",
357
+ " private final WebDriverWait wait;",
358
+ "",
359
+ ]
360
+
361
+ for loc in page.locators:
362
+ if loc.strategy == LocatorStrategy.XPATH:
363
+ code.append(f' private final By {loc.name} = By.xpath("{escape_java(loc.value)}");')
364
+ elif loc.strategy == LocatorStrategy.ID:
365
+ code.append(f' private final By {loc.name} = By.id("{escape_java(loc.value)}");')
366
+ else:
367
+ code.append(f' private final By {loc.name} = By.cssSelector("{escape_java(loc.value)}");')
368
+
369
+ code.extend([
370
+ "",
371
+ f" public {page.name}(WebDriver driver) {{",
372
+ " this.driver = driver;",
373
+ " this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));",
374
+ " }",
375
+ ])
376
+
377
+ for m in page.methods:
378
+ params_decl = ", ".join([f"String {p}" for p in m.parameters])
379
+ code.extend([
380
+ "",
381
+ f" public void {m.name}({params_decl}) {{",
382
+ ])
383
+ if not m.steps:
384
+ code.append(" // Stub")
385
+ for s in m.steps:
386
+ if s.action == ActionType.CLICK:
387
+ code.append(f" wait.until(ExpectedConditions.elementToBeClickable({s.target})).click();")
388
+ elif s.action == ActionType.FILL:
389
+ code.append(f' wait.until(ExpectedConditions.visibilityOfElementLocated({s.target})).sendKeys("{escape_java(s.value or "")}");')
390
+ else:
391
+ code.append(f" // {s.description}")
392
+ code.append(" }")
393
+
394
+ code.append("}\n")
395
+ files[f"src/main/java/pages/{page.name}.java"] = "\n".join(code)
396
+
397
+ return files
398
+
399
+ # -------------------------------------------------------------------------
400
+ # 4. Katalon Groovy Emitter (Clean POM Flat Architecture)
401
+ # -------------------------------------------------------------------------
402
+ @classmethod
403
+ def _emit_katalon_groovy(cls, project: ProjectIR) -> Dict[str, str]:
404
+ files: Dict[str, str] = {}
405
+
406
+ for page in project.pages:
407
+ code = [
408
+ "package pages",
409
+ "",
410
+ "import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI",
411
+ "import internal.GlobalVariable",
412
+ "",
413
+ f"public class {page.name} extends BasePage {{",
414
+ "",
415
+ ]
416
+
417
+ for loc in page.locators:
418
+ code.append(f' public static final String {loc.name.upper()} = "{escape_java(loc.value)}"')
419
+
420
+ code.append("")
421
+
422
+ for m in page.methods:
423
+ params_decl = ", ".join([f"String {p}" for p in m.parameters])
424
+ code.extend([
425
+ f" public static void {m.name}({params_decl}) {{",
426
+ ])
427
+ for s in m.steps:
428
+ if s.action == ActionType.CLICK:
429
+ code.append(f" clickJS({s.target.upper()})")
430
+ elif s.action == ActionType.FILL:
431
+ code.append(f' setTextSafe({s.target.upper()}, "{escape_java(s.value or "")}")')
432
+ elif s.action == ActionType.GOTO:
433
+ code.append(f' WebUI.navigateToUrl("{escape_java(s.value or "")}")')
434
+ else:
435
+ code.append(f" // {s.description}")
436
+ code.append(" }\n")
437
+
438
+ code.append("}\n")
439
+ files[f"Keywords/pages/{page.name}.groovy"] = "\n".join(code)
440
+
441
+ if project.test_cases:
442
+ for tc in project.test_cases:
443
+ s_code = [
444
+ "import static com.kms.katalon.core.testcase.TestCaseFactory.findTestCase",
445
+ "import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI",
446
+ "import pages.*",
447
+ "",
448
+ f"String uniqueCode = BasePage.uniqueCode('AUTO_')",
449
+ "",
450
+ ]
451
+ for s in tc.steps:
452
+ if s.action == ActionType.GOTO:
453
+ s_code.append(f'WebUI.navigateToUrl("{escape_java(s.value or "")}")')
454
+ elif s.action == ActionType.ASSERT:
455
+ s_code.append('WebUI.verifyElementPresent(BasePage.findTestObject("..."), 10)')
456
+ else:
457
+ s_code.append(f'// {s.description}')
458
+
459
+ s_code.append("")
460
+ files[f"Scripts/{tc.id}_{tc.title}/Script.groovy"] = "\n".join(s_code)
461
+
462
+ return files
463
+
464
+ # -------------------------------------------------------------------------
465
+ # 5. Cypress JavaScript Emitter
466
+ # -------------------------------------------------------------------------
467
+ @classmethod
468
+ def _emit_cypress_js(cls, project: ProjectIR) -> Dict[str, str]:
469
+ files: Dict[str, str] = {}
470
+
471
+ for page in project.pages:
472
+ code = [
473
+ f"export class {page.name} {{",
474
+ ]
475
+ for loc in page.locators:
476
+ code.extend([
477
+ f" get {loc.name}() {{",
478
+ f" return cy.get('{escape_js(loc.value)}');",
479
+ " }",
480
+ ])
481
+
482
+ for m in page.methods:
483
+ params_decl = ", ".join(m.parameters)
484
+ code.extend([
485
+ "",
486
+ f" {m.name}({params_decl}) {{",
487
+ ])
488
+ for s in m.steps:
489
+ if s.action == ActionType.CLICK:
490
+ code.append(f" this.{s.target}.click();")
491
+ elif s.action == ActionType.FILL:
492
+ code.append(f" this.{s.target}.type('{escape_js(s.value or '')}');")
493
+ else:
494
+ code.append(f" // {s.description}")
495
+ code.append(" }")
496
+
497
+ code.append("}\n")
498
+ files[f"cypress/pages/{page.name}.js"] = "\n".join(code)
499
+
500
+ return files