synapse-hub 0.0.0-stage → 2.0.0
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/failure-analyst.md +47 -0
- package/.agents/agents/live-explorer.md +41 -0
- package/.agents/agents/orchestrator.md +43 -0
- package/.agents/agents/pom-crafter.md +81 -0
- package/.agents/agents/quality-gatekeeper.md +47 -0
- package/.agents/agents/test-runner.md +43 -0
- package/.agents/rules/context-engineering.md +88 -0
- package/.agents/rules/git-push-protocol.md +101 -0
- package/.agents/rules/graphify.md +14 -0
- package/.agents/rules/lessons-learned.md +23 -0
- package/.agents/rules/mandatory-playwright-live.md +29 -0
- package/.agents/rules/skill-precedence.md +42 -0
- package/.agents/rules/systematic-debugging.md +99 -0
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
- package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
- package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
- package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
- package/.agents/skills/github-actions-templates/SKILL.md +334 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
- package/.agents/skills/lam-script-generator/SKILL.md +83 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
- package/.agents/skills/self-healing-locator/SKILL.md +73 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
- package/.agents/templates/completion_report.md +57 -0
- package/.agents/workflows/graphify.md +10 -0
- package/.agents/workflows/test-impact.md +15 -0
- package/.agents/workflows/verify.md +34 -0
- package/AGENTS.md +115 -0
- package/README.md +394 -2
- package/bin/agent-qa.js +90 -0
- package/package.json +59 -4
- package/skills_manifest.json +262 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
- package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
- package/tools/TOOLS_RUNBOOK.md +298 -0
- package/tools/agent_canary.py +501 -0
- package/tools/analyze_project.py +242 -0
- package/tools/api_drift_sentinel.py +208 -0
- package/tools/auto_heal.py +189 -0
- package/tools/browser_helpers.js +593 -0
- package/tools/bug_reporter.py +224 -0
- package/tools/cache_resolver.py +846 -0
- package/tools/check_env.py +288 -0
- package/tools/ci_healing_hook.py +128 -0
- package/tools/cli.py +402 -0
- package/tools/config_decoupler.py +253 -0
- package/tools/db_sandbox.py +341 -0
- package/tools/distill_log.py +152 -0
- package/tools/e2e_flow_prober.ps1 +65 -0
- package/tools/flaky_detector.py +222 -0
- package/tools/fuzz_payloads.py +197 -0
- package/tools/generate_allure_report.ps1 +30 -0
- package/tools/generate_api_matrix.py +193 -0
- package/tools/generate_tc_from_story.py +182 -0
- package/tools/generate_test_reports.ps1 +135 -0
- package/tools/git_pre_commit_guard.py +203 -0
- package/tools/heal_pr_automator.py +195 -0
- package/tools/heuristic_locator_matcher.py +202 -0
- package/tools/infer_linter.py +238 -0
- package/tools/lint_ai_docs.py +254 -0
- package/tools/lookup_tc.py +165 -0
- package/tools/lookup_xpath.py +167 -0
- package/tools/matrix_sharder.py +146 -0
- package/tools/mcp_excel_server.ps1 +251 -0
- package/tools/mcp_server.py +490 -0
- package/tools/micro_mutator.py +309 -0
- package/tools/network_mock_shield.py +170 -0
- package/tools/omni_emitter.py +500 -0
- package/tools/omni_parser.py +435 -0
- package/tools/omni_transpiler.py +168 -0
- package/tools/pinpoint_failure.py +88 -0
- package/tools/project_state.py +169 -0
- package/tools/repo_map.json +12 -0
- package/tools/repo_map.py +277 -0
- package/tools/repo_map.txt +7 -0
- package/tools/safety_firewall.py +245 -0
- package/tools/setup_project.py +265 -0
- package/tools/smart_visual_masker.py +134 -0
- package/tools/test_impact.py +321 -0
- package/tools/tms_sync_bridge.py +342 -0
- package/tools/trace_data_flow.py +240 -0
- package/tools/traffic_session_converter.py +256 -0
- package/tools/universal_ir.py +480 -0
- package/tools/validate_groovy_scripts.py +173 -0
- package/tools/verify_all.py +496 -0
- package/tools/verify_all.sh +4 -0
- package/tools/visual_diff_canvas.py +142 -0
- package/tools/visual_som_helper.py +418 -0
- package/tools/wcag_audit.py +214 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enterprise AI Documentation & Artifact Linter (Anti-Drift Guard v2.0)
|
|
4
|
+
---------------------------------------------------------------
|
|
5
|
+
Automatically cross-references AI skills, agent instructions, and markdown
|
|
6
|
+
documents against the actual repository files, classes, methods, and test cases.
|
|
7
|
+
|
|
8
|
+
Checks:
|
|
9
|
+
1. Page Class references: Matches against Keywords/pages/*.groovy.
|
|
10
|
+
2. Test Suite references: Matches against Test Suites/*.ts.
|
|
11
|
+
3. Tool script references: Matches against tools/*.
|
|
12
|
+
4. Test Case references: Matches against Test Cases/*.tc (and validates TC-1..259 range).
|
|
13
|
+
5. Method call references: Matches EnterpriseHelper.xxx() and BasePage.xxx() against real AST/regex methods.
|
|
14
|
+
|
|
15
|
+
Prevents AI agents from hallucinating nonexistent PageClasses, suites, tools, methods, or test cases.
|
|
16
|
+
Supports both Target Test Project mode and Hub Repository mode.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import glob
|
|
22
|
+
import re
|
|
23
|
+
|
|
24
|
+
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
25
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
26
|
+
try:
|
|
27
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
28
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
GENERIC_PAGES = {
|
|
33
|
+
"WebPage", "CurrentPage", "LandingPage", "GitHubPage", "SamplePage",
|
|
34
|
+
"BasePage", "Page", "SinglePage", "MultiPage"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def extract_groovy_methods(file_path):
|
|
38
|
+
"""Extracts standard public/static methods from a Groovy class file."""
|
|
39
|
+
methods = set()
|
|
40
|
+
if not os.path.exists(file_path):
|
|
41
|
+
return methods
|
|
42
|
+
try:
|
|
43
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as fp:
|
|
44
|
+
lines = fp.readlines()
|
|
45
|
+
method_pattern = re.compile(
|
|
46
|
+
r'^\s*(?:@\w+\s+)*(?:public|protected|private)?\s*(?:static\s+)?(?:final\s+)?'
|
|
47
|
+
r'(?:void|def|String|int|boolean|List[^\s]*|TestObject|WebDriver|[A-Za-z0-9_<>\[\]]+)\s+([a-z][a-zA-Z0-9_]*)\s*\('
|
|
48
|
+
)
|
|
49
|
+
for line in lines:
|
|
50
|
+
stripped = line.strip()
|
|
51
|
+
if stripped.startswith("//") or stripped.startswith("*"):
|
|
52
|
+
continue
|
|
53
|
+
m = method_pattern.search(line)
|
|
54
|
+
if m:
|
|
55
|
+
mname = m.group(1)
|
|
56
|
+
if mname not in {"if", "for", "while", "switch", "catch"}:
|
|
57
|
+
methods.add(mname)
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
return methods
|
|
61
|
+
|
|
62
|
+
def get_actual_entities(repo_root):
|
|
63
|
+
pages_dir = os.path.join(repo_root, "Keywords", "pages")
|
|
64
|
+
helpers_dir = os.path.join(repo_root, "Keywords", "helpers")
|
|
65
|
+
suites_dir = os.path.join(repo_root, "Test Suites")
|
|
66
|
+
cases_dir = os.path.join(repo_root, "Test Cases")
|
|
67
|
+
tools_dir = os.path.join(repo_root, "tools")
|
|
68
|
+
|
|
69
|
+
actual_pages = set()
|
|
70
|
+
class_methods = {}
|
|
71
|
+
|
|
72
|
+
if os.path.isdir(pages_dir):
|
|
73
|
+
for f in os.listdir(pages_dir):
|
|
74
|
+
if f.endswith(".groovy"):
|
|
75
|
+
pname = f[:-7]
|
|
76
|
+
actual_pages.add(pname)
|
|
77
|
+
class_methods[pname] = extract_groovy_methods(os.path.join(pages_dir, f))
|
|
78
|
+
|
|
79
|
+
if os.path.isdir(helpers_dir):
|
|
80
|
+
for f in os.listdir(helpers_dir):
|
|
81
|
+
if f.endswith(".groovy"):
|
|
82
|
+
hname = f[:-7]
|
|
83
|
+
class_methods[hname] = extract_groovy_methods(os.path.join(helpers_dir, f))
|
|
84
|
+
|
|
85
|
+
actual_suites = set()
|
|
86
|
+
if os.path.isdir(suites_dir):
|
|
87
|
+
for f in os.listdir(suites_dir):
|
|
88
|
+
if f.endswith(".ts"):
|
|
89
|
+
actual_suites.add(f[:-3])
|
|
90
|
+
|
|
91
|
+
actual_cases = set()
|
|
92
|
+
actual_tc_ids = set()
|
|
93
|
+
if os.path.isdir(cases_dir):
|
|
94
|
+
for f in os.listdir(cases_dir):
|
|
95
|
+
if f.endswith(".tc"):
|
|
96
|
+
base_name = f[:-3]
|
|
97
|
+
actual_cases.add(base_name)
|
|
98
|
+
m = re.match(r'(TC-\d+)', base_name)
|
|
99
|
+
if m:
|
|
100
|
+
actual_tc_ids.add(m.group(1))
|
|
101
|
+
|
|
102
|
+
actual_tools = set()
|
|
103
|
+
if os.path.isdir(tools_dir):
|
|
104
|
+
actual_tools.update(os.listdir(tools_dir))
|
|
105
|
+
# Always include Hub tools directory in decoupled architecture
|
|
106
|
+
hub_tools = os.path.abspath(os.path.dirname(__file__))
|
|
107
|
+
if os.path.isdir(hub_tools):
|
|
108
|
+
actual_tools.update(os.listdir(hub_tools))
|
|
109
|
+
|
|
110
|
+
return actual_pages, actual_suites, actual_cases, actual_tc_ids, actual_tools, class_methods
|
|
111
|
+
|
|
112
|
+
def find_doc_files(repo_root):
|
|
113
|
+
md_patterns = [
|
|
114
|
+
os.path.join(repo_root, ".agents", "**", "*.md"),
|
|
115
|
+
os.path.join(repo_root, "*.md"),
|
|
116
|
+
]
|
|
117
|
+
doc_files = []
|
|
118
|
+
for pattern in md_patterns:
|
|
119
|
+
for f in glob.glob(pattern, recursive=True):
|
|
120
|
+
rel = os.path.relpath(f, repo_root)
|
|
121
|
+
if "node_modules" in rel or ".git" in rel or "allure-results" in rel:
|
|
122
|
+
continue
|
|
123
|
+
doc_files.append(rel)
|
|
124
|
+
return sorted(doc_files)
|
|
125
|
+
|
|
126
|
+
def lint_docs(repo_root):
|
|
127
|
+
actual_pages, actual_suites, actual_cases, actual_tc_ids, actual_tools, class_methods = get_actual_entities(repo_root)
|
|
128
|
+
doc_files = find_doc_files(repo_root)
|
|
129
|
+
|
|
130
|
+
page_pattern = re.compile(r'\b([A-Z][A-Za-z0-9]+Page)\b')
|
|
131
|
+
suite_pattern = re.compile(r'(?:Test Suites/|-Suite\s+["\'](?:Test Suites/)?|--testSuitePath=["\'](?:Test Suites/)?)([A-Za-z0-9_]+)')
|
|
132
|
+
tool_pattern = re.compile(r'\btools/([a-zA-Z0-9_\-\.]+\.(?:py|sh|ps1|bat|js))\b')
|
|
133
|
+
tc_id_pattern = re.compile(r'\b(TC-\d+)\b')
|
|
134
|
+
tc_path_pattern = re.compile(r'Test Cases/([A-Za-z0-9_ \-\.\(\)]+?)(?:\.tc)?["\s\)]')
|
|
135
|
+
method_pattern = re.compile(r'\b(EnterpriseHelper|BasePage|[A-Z][a-zA-Z0-9]+Page)\.([a-z][a-zA-Z0-9_]+)\s*\(')
|
|
136
|
+
|
|
137
|
+
errors = []
|
|
138
|
+
warnings = []
|
|
139
|
+
|
|
140
|
+
# Ignored tool references (examples, output targets, artifacts)
|
|
141
|
+
ignored_tool_refs = {"api_matrix.json", "output.json", "report.json", "output.txt", "summary.json"}
|
|
142
|
+
|
|
143
|
+
for doc in doc_files:
|
|
144
|
+
full_path = os.path.join(repo_root, doc)
|
|
145
|
+
try:
|
|
146
|
+
with open(full_path, "r", encoding="utf-8", errors="ignore") as fp:
|
|
147
|
+
lines = fp.readlines()
|
|
148
|
+
except Exception as e:
|
|
149
|
+
errors.append(f"Cannot read {doc}: {e}")
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
is_historical = "FINAL_REPORT.md" in doc or "POM_REFACTORING_PLAN.md" in doc
|
|
153
|
+
|
|
154
|
+
for line_no, line in enumerate(lines, 1):
|
|
155
|
+
# 1. Page Class Lint (Only strictly enforced if repository defines Page Classes in Keywords/pages)
|
|
156
|
+
if actual_pages:
|
|
157
|
+
for m in page_pattern.finditer(line):
|
|
158
|
+
pname = m.group(1)
|
|
159
|
+
if pname not in actual_pages and pname not in GENERIC_PAGES:
|
|
160
|
+
if is_historical:
|
|
161
|
+
continue
|
|
162
|
+
errors.append(f"[{doc}:{line_no}] Non-existent Page Class referenced: '{pname}' (Must be one of {sorted(actual_pages)})")
|
|
163
|
+
|
|
164
|
+
# 2. Test Suite Lint (Only strictly enforced if repository defines Test Suites)
|
|
165
|
+
if actual_suites:
|
|
166
|
+
for m in suite_pattern.finditer(line):
|
|
167
|
+
sname = m.group(1)
|
|
168
|
+
if "xxx" in sname.lower() or "example" in sname.lower():
|
|
169
|
+
continue
|
|
170
|
+
if is_historical:
|
|
171
|
+
continue
|
|
172
|
+
if sname not in actual_suites:
|
|
173
|
+
errors.append(f"[{doc}:{line_no}] Non-existent Test Suite referenced: '{sname}'")
|
|
174
|
+
|
|
175
|
+
# 3. Tool Script Lint (Enforced in both Hub and Target modes)
|
|
176
|
+
for m in tool_pattern.finditer(line):
|
|
177
|
+
tname = m.group(1)
|
|
178
|
+
if tname in ignored_tool_refs:
|
|
179
|
+
continue
|
|
180
|
+
if tname not in actual_tools:
|
|
181
|
+
errors.append(f"[{doc}:{line_no}] Non-existent tool script referenced: 'tools/{tname}'")
|
|
182
|
+
|
|
183
|
+
# 4. Test Case Validation (Only strictly enforced if repository defines Test Cases)
|
|
184
|
+
if actual_cases and not is_historical:
|
|
185
|
+
# 4a. Validate TC-xxx numeric range
|
|
186
|
+
for m in tc_id_pattern.finditer(line):
|
|
187
|
+
tcid = m.group(1)
|
|
188
|
+
num = int(tcid[3:])
|
|
189
|
+
if num < 1 or num > 259:
|
|
190
|
+
errors.append(f"[{doc}:{line_no}] Out-of-range Test Case ID referenced: '{tcid}' (Valid range: TC-1 to TC-259)")
|
|
191
|
+
|
|
192
|
+
# 4b. Validate full Test Cases/... paths
|
|
193
|
+
for m in tc_path_pattern.finditer(line):
|
|
194
|
+
tcname = m.group(1).strip()
|
|
195
|
+
if "xxx" in tcname.lower() or "*" in tcname or "example" in tcname.lower():
|
|
196
|
+
continue
|
|
197
|
+
if tcname.endswith(".tc"):
|
|
198
|
+
tcname = tcname[:-3]
|
|
199
|
+
if tcname not in actual_cases:
|
|
200
|
+
# Check prefix match
|
|
201
|
+
matching = [c for c in actual_cases if c.startswith(tcname)]
|
|
202
|
+
if not matching:
|
|
203
|
+
errors.append(f"[{doc}:{line_no}] Non-existent Test Case path referenced: 'Test Cases/{tcname}'")
|
|
204
|
+
|
|
205
|
+
# 5. Method Call Lint (Cross-checks Class.method() against Groovy AST/regex)
|
|
206
|
+
for m in method_pattern.finditer(line):
|
|
207
|
+
cname = m.group(1)
|
|
208
|
+
mname = m.group(2)
|
|
209
|
+
if "xxx" in mname.lower() or "example" in mname.lower():
|
|
210
|
+
continue
|
|
211
|
+
if cname in class_methods:
|
|
212
|
+
valid_methods = class_methods[cname]
|
|
213
|
+
# Also include inherited BasePage methods if it's a Page class
|
|
214
|
+
if cname != "BasePage" and "BasePage" in class_methods:
|
|
215
|
+
valid_methods = valid_methods.union(class_methods["BasePage"])
|
|
216
|
+
|
|
217
|
+
if mname not in valid_methods:
|
|
218
|
+
errors.append(f"[{doc}:{line_no}] Non-existent method referenced: '{cname}.{mname}()' (Class '{cname}' does not define this method)")
|
|
219
|
+
|
|
220
|
+
return errors, warnings
|
|
221
|
+
|
|
222
|
+
def main():
|
|
223
|
+
import argparse
|
|
224
|
+
parser = argparse.ArgumentParser(description="Enterprise AI Documentation & Artifact Linter")
|
|
225
|
+
parser.add_argument("target", nargs="?", default=None, help="Path to repository root to lint")
|
|
226
|
+
parser.add_argument("--repo", default=None, help="Path to repository root to lint")
|
|
227
|
+
args = parser.parse_args()
|
|
228
|
+
|
|
229
|
+
target_path = args.repo or args.target
|
|
230
|
+
if target_path:
|
|
231
|
+
repo_root = os.path.abspath(target_path)
|
|
232
|
+
else:
|
|
233
|
+
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
234
|
+
|
|
235
|
+
print(f"[AI-DOCS-LINT] Running Documentation & Artifact Linter on: {os.path.basename(repo_root)}")
|
|
236
|
+
errors, warnings = lint_docs(repo_root)
|
|
237
|
+
|
|
238
|
+
if warnings:
|
|
239
|
+
print(f"\n[WARN] {len(warnings)} Warnings:")
|
|
240
|
+
for w in warnings:
|
|
241
|
+
print(f" - {w}")
|
|
242
|
+
|
|
243
|
+
if errors:
|
|
244
|
+
print(f"\n[FAIL] {len(errors)} Lint Errors Found (Documentation Drift / Hallucination):")
|
|
245
|
+
for e in errors:
|
|
246
|
+
print(f" - {e}")
|
|
247
|
+
print("\n[BLOCKED] Verification failed: Please align documentation with real disk entities.")
|
|
248
|
+
return 1
|
|
249
|
+
else:
|
|
250
|
+
print("[PASS] All documented tools, PageClasses, Suites, and Method calls verified on disk.")
|
|
251
|
+
return 0
|
|
252
|
+
|
|
253
|
+
if __name__ == "__main__":
|
|
254
|
+
sys.exit(main())
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enterprise QA: Fast Test Case Metadata Lookup (lookup_tc.py)
|
|
4
|
+
-------------------------------------------------------------
|
|
5
|
+
Fast lookup tool for Test Cases from project JSON testcase specifications.
|
|
6
|
+
Enables AI Agents to load a single targeted test case (<100 tokens)
|
|
7
|
+
instead of reading entire massive test suites (~45,000 tokens).
|
|
8
|
+
|
|
9
|
+
Multi-tier Resolution:
|
|
10
|
+
1. CLI Flag: python tools/lookup_tc.py <query> --file <path_to_json> (-f)
|
|
11
|
+
2. Environment variable: TC_DATA_FILE or TESTCASES_JSON
|
|
12
|
+
3. Automatic discovery: testcases.json, tc_index.json, test_cases.json...
|
|
13
|
+
4. Internal Hub archive fallback: archive/legacy_references/Enterprise_testcases.json
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import argparse
|
|
20
|
+
|
|
21
|
+
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
22
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
23
|
+
try:
|
|
24
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
25
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_json_path(cli_file=None):
|
|
31
|
+
"""Resolves Test Case JSON file path via precedence order."""
|
|
32
|
+
# 1. CLI flag
|
|
33
|
+
if cli_file:
|
|
34
|
+
if os.path.exists(cli_file):
|
|
35
|
+
return os.path.abspath(cli_file)
|
|
36
|
+
else:
|
|
37
|
+
print(f"⚠️ Warning: File specified via --file '{cli_file}' does not exist.")
|
|
38
|
+
|
|
39
|
+
# 2. Environment variable
|
|
40
|
+
env_file = os.environ.get("TC_DATA_FILE") or os.environ.get("TESTCASES_JSON")
|
|
41
|
+
if env_file and os.path.exists(env_file):
|
|
42
|
+
return os.path.abspath(env_file)
|
|
43
|
+
|
|
44
|
+
# 3. Automatic discovery in CWD and common directories
|
|
45
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
46
|
+
hub_root = os.path.abspath(os.path.join(script_dir, ".."))
|
|
47
|
+
cwd = os.getcwd()
|
|
48
|
+
|
|
49
|
+
search_dirs = [
|
|
50
|
+
cwd,
|
|
51
|
+
os.path.join(cwd, "references"),
|
|
52
|
+
os.path.join(cwd, "data"),
|
|
53
|
+
os.path.join(cwd, "tools"),
|
|
54
|
+
os.path.join(cwd, "docs"),
|
|
55
|
+
script_dir
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
candidate_names = [
|
|
59
|
+
"testcases.json",
|
|
60
|
+
"tc_index.json",
|
|
61
|
+
"Enterprise_testcases.json",
|
|
62
|
+
"test_cases.json",
|
|
63
|
+
"tc_matrix.json"
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
for d in search_dirs:
|
|
67
|
+
for name in candidate_names:
|
|
68
|
+
candidate = os.path.join(d, name)
|
|
69
|
+
if os.path.isfile(candidate):
|
|
70
|
+
return os.path.abspath(candidate)
|
|
71
|
+
|
|
72
|
+
# 4. Hub Archive fallback
|
|
73
|
+
for legacy_name in ["Enterprise_testcases.json", "dmit_testcases.json", "odp_testcases.json"]:
|
|
74
|
+
legacy_file = os.path.join(hub_root, "archive", "legacy_references", legacy_name)
|
|
75
|
+
if os.path.isfile(legacy_file):
|
|
76
|
+
return os.path.abspath(legacy_file)
|
|
77
|
+
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def normalize_testcase_data(raw_data):
|
|
82
|
+
"""Normalizes Test Case data across diverse JSON structures (list, dict, dict of dicts)."""
|
|
83
|
+
if isinstance(raw_data, list):
|
|
84
|
+
return raw_data
|
|
85
|
+
elif isinstance(raw_data, dict):
|
|
86
|
+
if "testcases" in raw_data and isinstance(raw_data["testcases"], list):
|
|
87
|
+
return raw_data["testcases"]
|
|
88
|
+
if "items" in raw_data and isinstance(raw_data["items"], list):
|
|
89
|
+
return raw_data["items"]
|
|
90
|
+
|
|
91
|
+
flat = []
|
|
92
|
+
for k, v in raw_data.items():
|
|
93
|
+
if isinstance(v, dict):
|
|
94
|
+
item = dict(v)
|
|
95
|
+
if "ID" not in item:
|
|
96
|
+
item["ID"] = k
|
|
97
|
+
flat.append(item)
|
|
98
|
+
else:
|
|
99
|
+
flat.append({"ID": k, "Name": str(v)})
|
|
100
|
+
return flat
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main():
|
|
105
|
+
parser = argparse.ArgumentParser(description="Fast Test Case lookup from project JSON specifications")
|
|
106
|
+
parser.add_argument("query", help="Test Case ID (e.g., TC-132, 132) or search keyword")
|
|
107
|
+
parser.add_argument("-f", "--file", dest="file", help="Path to JSON file containing test cases")
|
|
108
|
+
args = parser.parse_args()
|
|
109
|
+
|
|
110
|
+
json_path = resolve_json_path(args.file)
|
|
111
|
+
|
|
112
|
+
if not json_path:
|
|
113
|
+
print("❌ Error: Test Case JSON data file not found.")
|
|
114
|
+
print("\n👉 Guidance to configure test case data for target project:")
|
|
115
|
+
print(" 1. Use CLI flag: python tools/lookup_tc.py <query> --file <path_to_testcases.json>")
|
|
116
|
+
print(" 2. Set environment variable: export TC_DATA_FILE='path/to/testcases.json' (or set TC_DATA_FILE=...)")
|
|
117
|
+
print(" 3. Place 'testcases.json' in project root or declare path in PROJECT_AI_GUIDE.md")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
|
122
|
+
raw_data = json.load(f)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print(f"❌ Error reading file '{json_path}': {e}")
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
data = normalize_testcase_data(raw_data)
|
|
128
|
+
query = args.query.strip().lower()
|
|
129
|
+
|
|
130
|
+
exact_matches = []
|
|
131
|
+
fuzzy_matches = []
|
|
132
|
+
|
|
133
|
+
for tc in data:
|
|
134
|
+
tc_id = str(tc.get("ID") or tc.get("id") or tc.get("tc_id") or "").strip().lower()
|
|
135
|
+
tc_name = str(tc.get("Name") or tc.get("name") or tc.get("title") or "").strip().lower()
|
|
136
|
+
|
|
137
|
+
if query == tc_id or f"tc-{query}" == tc_id or f"tc_{query}" == tc_id:
|
|
138
|
+
exact_matches.append(tc)
|
|
139
|
+
elif query in tc_id or query in tc_name:
|
|
140
|
+
fuzzy_matches.append(tc)
|
|
141
|
+
|
|
142
|
+
matches = exact_matches if exact_matches else fuzzy_matches
|
|
143
|
+
|
|
144
|
+
if not matches:
|
|
145
|
+
print(f"No Test Cases found matching: '{args.query}' in file '{os.path.basename(json_path)}'")
|
|
146
|
+
sys.exit(0)
|
|
147
|
+
|
|
148
|
+
rel_source = os.path.relpath(json_path, os.getcwd()) if os.path.exists(json_path) else json_path
|
|
149
|
+
print(f"=== Found {len(matches)} Test Case(s) (Source: {rel_source}) ===")
|
|
150
|
+
for tc in matches[:5]:
|
|
151
|
+
tc_id = tc.get("ID") or tc.get("id") or "N/A"
|
|
152
|
+
tc_mod = tc.get("Module") or tc.get("module") or "GLOBAL"
|
|
153
|
+
tc_name = tc.get("Name") or tc.get("name") or tc.get("title") or "N/A"
|
|
154
|
+
tc_expected = tc.get("Expected") or tc.get("expected") or tc.get("expected_result") or "N/A"
|
|
155
|
+
tc_steps = tc.get("Steps") or tc.get("steps") or tc.get("description") or "N/A"
|
|
156
|
+
|
|
157
|
+
print(f"ID: {tc_id} | Module: {tc_mod}")
|
|
158
|
+
print(f"Name: {tc_name}")
|
|
159
|
+
print(f"Expected: {tc_expected}")
|
|
160
|
+
print(f"Steps:\n{tc_steps}")
|
|
161
|
+
print("-" * 50)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
main()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Enterprise QA: Fast XPath / Selector Lookup (lookup_xpath.py)
|
|
4
|
+
-------------------------------------------------------------
|
|
5
|
+
Fast lookup tool for XPaths and selectors from Page Class JSON specifications.
|
|
6
|
+
Enables AI Agents to retrieve only the relevant selectors (<300 tokens)
|
|
7
|
+
instead of reading entire selector repositories or rescanning live DOM.
|
|
8
|
+
|
|
9
|
+
Multi-tier Resolution:
|
|
10
|
+
1. CLI Flag: python tools/lookup_xpath.py <query> --file <path_to_json> (-f)
|
|
11
|
+
2. Environment variable: XPATH_DATA_FILE, SELECTORS_JSON, or LOCATORS_JSON
|
|
12
|
+
3. Automatic discovery: selectors.json, locators.json, scraped_xpaths.json...
|
|
13
|
+
4. Internal Hub archive fallback: archive/legacy_references/Enterprise_scraped_xpaths.json
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import argparse
|
|
20
|
+
|
|
21
|
+
# Enforce UTF-8 stdout encoding for Windows console compatibility
|
|
22
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
23
|
+
try:
|
|
24
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
25
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_json_path(cli_file=None):
|
|
31
|
+
"""Resolves Selector/XPath JSON file path via precedence order."""
|
|
32
|
+
# 1. CLI flag
|
|
33
|
+
if cli_file:
|
|
34
|
+
if os.path.exists(cli_file):
|
|
35
|
+
return os.path.abspath(cli_file)
|
|
36
|
+
else:
|
|
37
|
+
print(f"⚠️ Warning: File specified via --file '{cli_file}' does not exist.")
|
|
38
|
+
|
|
39
|
+
# 2. Environment variable
|
|
40
|
+
env_file = os.environ.get("XPATH_DATA_FILE") or os.environ.get("SELECTORS_JSON") or os.environ.get("LOCATORS_JSON")
|
|
41
|
+
if env_file and os.path.exists(env_file):
|
|
42
|
+
return os.path.abspath(env_file)
|
|
43
|
+
|
|
44
|
+
# 3. Automatic discovery in CWD and common directories
|
|
45
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
46
|
+
hub_root = os.path.abspath(os.path.join(script_dir, ".."))
|
|
47
|
+
cwd = os.getcwd()
|
|
48
|
+
|
|
49
|
+
search_dirs = [
|
|
50
|
+
cwd,
|
|
51
|
+
os.path.join(cwd, "references"),
|
|
52
|
+
os.path.join(cwd, "data"),
|
|
53
|
+
os.path.join(cwd, "tools"),
|
|
54
|
+
os.path.join(cwd, "docs"),
|
|
55
|
+
script_dir
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
candidate_names = [
|
|
59
|
+
"selectors.json",
|
|
60
|
+
"scraped_xpaths.json",
|
|
61
|
+
"locators.json",
|
|
62
|
+
"Enterprise_scraped_xpaths.json",
|
|
63
|
+
"page_selectors.json",
|
|
64
|
+
"elements.json"
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for d in search_dirs:
|
|
68
|
+
for name in candidate_names:
|
|
69
|
+
candidate = os.path.join(d, name)
|
|
70
|
+
if os.path.isfile(candidate):
|
|
71
|
+
return os.path.abspath(candidate)
|
|
72
|
+
|
|
73
|
+
# 4. Hub Archive fallback
|
|
74
|
+
for legacy_name in ["Enterprise_scraped_xpaths.json", "dmit_scraped_xpaths.json", "batch01_scraped_xpaths.json"]:
|
|
75
|
+
legacy_file = os.path.join(hub_root, "archive", "legacy_references", legacy_name)
|
|
76
|
+
if os.path.isfile(legacy_file):
|
|
77
|
+
return os.path.abspath(legacy_file)
|
|
78
|
+
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def normalize_selector_data(raw_data):
|
|
83
|
+
"""Normalizes Selector data across PageClass dictionary or flat list schemas."""
|
|
84
|
+
if isinstance(raw_data, dict):
|
|
85
|
+
return raw_data
|
|
86
|
+
elif isinstance(raw_data, list):
|
|
87
|
+
grouped = {}
|
|
88
|
+
for item in raw_data:
|
|
89
|
+
page = item.get("page") or item.get("page_class") or "GLOBAL"
|
|
90
|
+
if page not in grouped:
|
|
91
|
+
grouped[page] = []
|
|
92
|
+
grouped[page].append(item)
|
|
93
|
+
return grouped
|
|
94
|
+
return {}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def main():
|
|
98
|
+
parser = argparse.ArgumentParser(description="Fast Selector / XPath lookup from project JSON specifications")
|
|
99
|
+
parser.add_argument("query", help="PageClass name or selector identifier keyword (e.g., TargetPlanPage, TXT_CODE)")
|
|
100
|
+
parser.add_argument("-f", "--file", dest="file", help="Path to JSON file containing selector/xpaths")
|
|
101
|
+
args = parser.parse_args()
|
|
102
|
+
|
|
103
|
+
json_path = resolve_json_path(args.file)
|
|
104
|
+
|
|
105
|
+
if not json_path:
|
|
106
|
+
print("❌ Error: Selector / XPath JSON data file not found.")
|
|
107
|
+
print("\n👉 Guidance to configure selector data for target project:")
|
|
108
|
+
print(" 1. Use CLI flag: python tools/lookup_xpath.py <query> --file <path_to_selectors.json>")
|
|
109
|
+
print(" 2. Set environment variable: export XPATH_DATA_FILE='path/to/selectors.json' (or set XPATH_DATA_FILE=...)")
|
|
110
|
+
print(" 3. Place 'selectors.json' in project root or declare path in PROJECT_AI_GUIDE.md")
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
|
115
|
+
raw_data = json.load(f)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"❌ Error reading file '{json_path}': {e}")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
data = normalize_selector_data(raw_data)
|
|
121
|
+
query = args.query.strip()
|
|
122
|
+
|
|
123
|
+
# 1. Match by Page Class name
|
|
124
|
+
matched_page = None
|
|
125
|
+
for page_name in data.keys():
|
|
126
|
+
if query.lower() == page_name.lower():
|
|
127
|
+
matched_page = page_name
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
if matched_page:
|
|
131
|
+
selectors = data[matched_page]
|
|
132
|
+
source_label = os.path.relpath(json_path, os.getcwd()) if os.path.exists(json_path) else json_path
|
|
133
|
+
print(f"=== {matched_page} ({len(selectors)} selectors) [Source: {source_label}] ===")
|
|
134
|
+
for item in selectors:
|
|
135
|
+
c = item.get("constant") or item.get("name") or "SELECTOR"
|
|
136
|
+
tag = item.get("tag") or "element"
|
|
137
|
+
xp = item.get("xpath") or item.get("selector") or item.get("css") or ""
|
|
138
|
+
print(f"- {c} ({tag}): {xp}")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# 2. Match keyword across constant or xpath
|
|
142
|
+
found = []
|
|
143
|
+
for page_name, selectors in data.items():
|
|
144
|
+
if isinstance(selectors, list):
|
|
145
|
+
for item in selectors:
|
|
146
|
+
c = item.get("constant") or item.get("name") or ""
|
|
147
|
+
xp = item.get("xpath") or item.get("selector") or item.get("css") or ""
|
|
148
|
+
tag = item.get("tag") or "element"
|
|
149
|
+
if query.lower() in c.lower() or query.lower() in xp.lower():
|
|
150
|
+
found.append((page_name, c, tag, xp))
|
|
151
|
+
elif isinstance(selectors, dict):
|
|
152
|
+
for c, xp in selectors.items():
|
|
153
|
+
if query.lower() in c.lower() or query.lower() in str(xp).lower():
|
|
154
|
+
found.append((page_name, c, "element", str(xp)))
|
|
155
|
+
|
|
156
|
+
if not found:
|
|
157
|
+
print(f"No selectors found matching: '{query}' in file '{os.path.basename(json_path)}'")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
source_label = os.path.relpath(json_path, os.getcwd()) if os.path.exists(json_path) else json_path
|
|
161
|
+
print(f"=== Found {len(found)} selector(s) for query '{query}' [Source: {source_label}] ===")
|
|
162
|
+
for page, c, tag, xp in found[:15]:
|
|
163
|
+
print(f"[{page}] {c} ({tag}): {xp}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
main()
|