synapse-hub 2.2.4 → 2.4.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.
Files changed (311) hide show
  1. package/.agents/agents/failure-analyst.md +11 -0
  2. package/.agents/agents/live-explorer.md +28 -0
  3. package/.agents/agents/orchestrator.md +20 -4
  4. package/.agents/agents/pom-crafter.md +12 -0
  5. package/.agents/agents/quality-gatekeeper.md +19 -4
  6. package/.agents/agents/test-runner.md +11 -0
  7. package/.agents/rules/communication-style.md +40 -0
  8. package/.agents/rules/context-engineering.md +23 -0
  9. package/.agents/rules/git-push-protocol.md +10 -16
  10. package/.agents/rules/graphify.md +14 -14
  11. package/.agents/rules/mandatory-playwright-live.md +29 -29
  12. package/.agents/rules/mobile_appium_rules.md +132 -0
  13. package/.agents/rules/performance_testing_rules.md +74 -0
  14. package/.agents/rules/systematic-debugging.md +6 -6
  15. package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +64 -215
  16. package/.agents/skills/agent-orchestration-multi-agent-optimize/examples/swarm_workload_config.json +12 -0
  17. package/.agents/skills/antigravity-test-sentinel/SKILL.md +3 -3
  18. package/.agents/skills/antigravity-test-sentinel/examples/dod_gate_policy.json +11 -0
  19. package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
  20. package/.agents/skills/api-network-mocking-interception/examples/network_route_stub.json +9 -0
  21. package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +9 -9
  22. package/.agents/skills/api-spec-to-e2e-matrix/examples/sample_openapi.json +19 -0
  23. package/.agents/skills/architecture-decision-records/SKILL.md +35 -412
  24. package/.agents/skills/architecture-decision-records/examples/0001-record-architecture-decisions.md +12 -0
  25. package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
  26. package/.agents/skills/auth-session-and-cookie-manager/examples/storage_state_sample.json +11 -0
  27. package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +80 -186
  28. package/.agents/skills/backend-load-and-stress-patterns/examples/k6_load_test_sample.js +13 -0
  29. package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
  30. package/.agents/skills/bats-testing-patterns/examples/cli_runner_test.bats +6 -0
  31. package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
  32. package/.agents/skills/clean-pom-crafter/SKILL.md +21 -3
  33. package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
  34. package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
  35. package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +2 -2
  36. package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_page_sample.groovy +9 -0
  37. package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +5 -5
  38. package/.agents/skills/db-state-verifier-and-seeder/examples/db_fixture_seed.json +12 -0
  39. package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
  40. package/.agents/skills/e2e-testing-patterns/examples/playwright_sharded_config.ts +12 -0
  41. package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
  42. package/.agents/skills/failure-autopsy-and-healing/SKILL.md +17 -3
  43. package/.agents/skills/failure-autopsy-and-healing/examples/sample_junit_failure.xml +8 -0
  44. package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
  45. package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
  46. package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
  47. package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
  48. package/.agents/skills/file-and-report-validator/examples/sample_export_schema.json +14 -0
  49. package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +3 -3
  50. package/.agents/skills/flaky-test-sentinel-and-quarantine/examples/quarantine_registry.json +11 -0
  51. package/.agents/skills/gherkin-to-suite/SKILL.md +103 -0
  52. package/.agents/skills/gherkin-to-suite/examples/checkout.feature +39 -0
  53. package/.agents/skills/gherkin-to-suite/examples/suite_report_sample.json +334 -0
  54. package/.agents/skills/github-actions-templates/SKILL.md +47 -304
  55. package/.agents/skills/github-actions-templates/examples/matrix_test_pipeline.yml +8 -0
  56. package/.agents/skills/graphify-impact-analyzer/SKILL.md +6 -6
  57. package/.agents/skills/graphify-impact-analyzer/examples/dependency_blast_radius.json +8 -0
  58. package/.agents/skills/katalon-cli-runner/SKILL.md +2 -2
  59. package/.agents/skills/katalon-cli-runner/examples/headless_kre_exec.sh +3 -0
  60. package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
  61. package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
  62. package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
  63. package/.agents/skills/lam-script-generator/SKILL.md +42 -1
  64. package/.agents/skills/lam-script-generator/examples/user_story_sample.md +8 -0
  65. package/.agents/skills/lean-code-ladder/SKILL.md +83 -0
  66. package/.agents/skills/lean-code-ladder/examples/ladder_walkthrough.md +37 -0
  67. package/.agents/skills/meta-prompt-crafter/SKILL.md +107 -0
  68. package/.agents/skills/meta-prompt-crafter/examples/qa_prompt_template.md +35 -0
  69. package/.agents/skills/persistent-planning-files/SKILL.md +108 -0
  70. package/.agents/skills/persistent-planning-files/examples/task_plan_example.md +56 -0
  71. package/.agents/skills/playwright-live-interaction/SKILL.md +49 -1
  72. package/.agents/skills/playwright-live-interaction/examples/live_probe_script.js +5 -0
  73. package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
  74. package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
  75. package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
  76. package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
  77. package/.agents/skills/playwright-webapp-testing/SKILL.md +93 -0
  78. package/.agents/skills/playwright-webapp-testing/examples/server_lifecycle_playwright.py +63 -0
  79. package/.agents/skills/rbt-manual-testing/SKILL.md +150 -0
  80. package/.agents/skills/rbt-manual-testing/examples/checkout_rbt_manual_suite.md +25 -0
  81. package/.agents/skills/security-dast-sentinel/SKILL.md +54 -135
  82. package/.agents/skills/security-dast-sentinel/examples/dast_scan_config.json +10 -0
  83. package/.agents/skills/self-healing-locator/SKILL.md +24 -10
  84. package/.agents/skills/self-healing-locator/examples/aom_diff_sample.json +16 -0
  85. package/.agents/skills/skillspector-security-guard/SKILL.md +32 -90
  86. package/.agents/skills/skillspector-security-guard/examples/safe_mcp_config.json +12 -0
  87. package/.agents/skills/surgical-coding-principles/SKILL.md +89 -0
  88. package/.agents/skills/surgical-coding-principles/examples/surgical_vs_scope_creep.md +46 -0
  89. package/.agents/skills/synthetic-test-data-generator/SKILL.md +3 -3
  90. package/.agents/skills/synthetic-test-data-generator/examples/idempotent_data_recipe.json +9 -0
  91. package/.agents/skills/systematic-debugging/SKILL.md +100 -0
  92. package/.agents/skills/systematic-debugging/examples/four_phase_case.md +44 -0
  93. package/.agents/skills/test-driven-development/SKILL.md +114 -0
  94. package/.agents/skills/test-driven-development/examples/red_green_refactor_qa.md +78 -0
  95. package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
  96. package/.agents/skills/token-and-context-optimizer/examples/distilled_log_sample.txt +3 -0
  97. package/.agents/skills/verification-before-completion/SKILL.md +100 -0
  98. package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +46 -0
  99. package/.agents/skills/visual-regression-sentinel/SKILL.md +21 -1
  100. package/.agents/skills/visual-regression-sentinel/examples/visual_mask_rules.json +9 -0
  101. package/.agents/skills/wcag-audit-patterns/SKILL.md +3 -3
  102. package/.agents/skills/wcag-audit-patterns/examples/wcag_compliance_checklist.json +10 -0
  103. package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
  104. package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
  105. package/.agents/skills/web-vitals-and-performance-profiler/examples/cwv_budget_policy.json +6 -0
  106. package/.agents/templates/completion_report.md +57 -57
  107. package/.agents/workflows/analyze_requirement_document.md +18 -0
  108. package/.agents/workflows/export_testcases_excel.md +32 -0
  109. package/.agents/workflows/generate_combinatorial_test_data.md +28 -0
  110. package/.agents/workflows/generate_cross_module_test_plan.md +37 -0
  111. package/.agents/workflows/generate_manual_testcases_rbt.md +56 -0
  112. package/.agents/workflows/generate_requirements_from_website.md +25 -0
  113. package/.agents/workflows/generate_testcases_from_requirements.md +21 -0
  114. package/.agents/workflows/graphify.md +10 -10
  115. package/.agents/workflows/verify.md +2 -2
  116. package/.cursor/rules/language.mdc +7 -7
  117. package/.cursor/rules/synapse-qa.mdc +23 -0
  118. package/.cursorrules +7 -0
  119. package/.pre-commit-hooks.yaml +14 -0
  120. package/.windsurfrules +7 -0
  121. package/AGENTS.md +54 -15
  122. package/CLAUDE.md +35 -12
  123. package/LICENSE +30 -0
  124. package/NOTICE +180 -0
  125. package/README.en.md +325 -0
  126. package/README.md +247 -516
  127. package/action.yml +51 -0
  128. package/benchmarks/qa_eval/README.md +71 -0
  129. package/benchmarks/qa_eval/build_dataset.py +677 -0
  130. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +22 -0
  131. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +22 -0
  132. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +22 -0
  133. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +22 -0
  134. package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +22 -0
  135. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +21 -0
  136. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +21 -0
  137. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +21 -0
  138. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +21 -0
  139. package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +21 -0
  140. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +23 -0
  141. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +23 -0
  142. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +23 -0
  143. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +23 -0
  144. package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +23 -0
  145. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +22 -0
  146. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +22 -0
  147. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +22 -0
  148. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +22 -0
  149. package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +23 -0
  150. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +22 -0
  151. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +22 -0
  152. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +22 -0
  153. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +22 -0
  154. package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +22 -0
  155. package/benchmarks/qa_eval/eval_runner.py +418 -0
  156. package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +83 -0
  157. package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +69 -0
  158. package/benchmarks/qa_eval/reports/eval_results.json +105 -0
  159. package/bin/agent-qa.js +10 -3
  160. package/docs/ARCHITECTURE.md +107 -0
  161. package/docs/CI_CD_INTEGRATION.md +94 -0
  162. package/docs/CLI_REFERENCE.md +157 -0
  163. package/docs/CONTRIBUTING.md +91 -0
  164. package/docs/PLUGIN_DEVELOPMENT_GUIDE.md +126 -0
  165. package/docs/TOKEN_QUOTA_PLAYBOOK.md +41 -0
  166. package/docs/adopted-ecosystem-tools.md +87 -0
  167. package/docs/schemas/synapse.config.schema.json +64 -0
  168. package/examples/cypress-ecommerce-suite/README.md +45 -0
  169. package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +17 -0
  170. package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +29 -0
  171. package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +51 -0
  172. package/examples/cypress-ecommerce-suite/cypress.config.js +13 -0
  173. package/examples/cypress-ecommerce-suite/package.json +13 -0
  174. package/examples/katalon-suite/Keywords/pages/BasePage.groovy +19 -0
  175. package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +29 -0
  176. package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +45 -0
  177. package/examples/katalon-suite/Profiles/default.glbl +12 -0
  178. package/examples/katalon-suite/README.md +18 -0
  179. package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +12 -0
  180. package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +15 -0
  181. package/examples/katalon-suite/katalon_suite.prj +21 -0
  182. package/examples/playwright-crm-suite/README.md +40 -0
  183. package/examples/playwright-crm-suite/package.json +14 -0
  184. package/examples/playwright-crm-suite/pages/BasePage.ts +37 -0
  185. package/examples/playwright-crm-suite/pages/CustomerPage.ts +57 -0
  186. package/examples/playwright-crm-suite/pages/LoginPage.ts +32 -0
  187. package/examples/playwright-crm-suite/playwright.config.ts +27 -0
  188. package/examples/playwright-crm-suite/tests/auth.spec.ts +22 -0
  189. package/examples/playwright-crm-suite/tests/customers.spec.ts +33 -0
  190. package/examples/plugins/sample-custom-plugin/README.md +14 -0
  191. package/examples/plugins/sample-custom-plugin/plugin.json +16 -0
  192. package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +29 -0
  193. package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +8 -0
  194. package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +125 -0
  195. package/examples/practices/README.md +22 -0
  196. package/examples/practices/requirements/crm/requirements_contracts.md +292 -0
  197. package/examples/practices/requirements/crm/requirements_customers.md +462 -0
  198. package/examples/practices/requirements/crm/requirements_login.md +202 -0
  199. package/examples/practices/requirements/crm/requirements_projects.md +396 -0
  200. package/examples/practices/requirements/crm/requirements_tasks.md +343 -0
  201. package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +402 -0
  202. package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
  203. package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +52 -0
  204. package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
  205. package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
  206. package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +119 -0
  207. package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +9 -0
  208. package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
  209. package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
  210. package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +32 -0
  211. package/examples/practices/testcases/api/book_management_api_test_cases.md +144 -0
  212. package/examples/practices/testcases/api/user_management_api_test_cases.md +139 -0
  213. package/examples/practices/testcases/crm/test_cases_crm_login.md +148 -0
  214. package/examples/practices/testcases/crm/test_cases_customers.md +156 -0
  215. package/examples/pytest-api-suite/README.md +45 -0
  216. package/examples/pytest-api-suite/conftest.py +31 -0
  217. package/examples/pytest-api-suite/pages/user_api_client.py +36 -0
  218. package/examples/pytest-api-suite/requirements.txt +2 -0
  219. package/examples/pytest-api-suite/tests/test_user_api.py +44 -0
  220. package/examples/selenium-suite/README.md +18 -0
  221. package/examples/selenium-suite/pages/base_page.py +27 -0
  222. package/examples/selenium-suite/pages/login_page.py +29 -0
  223. package/examples/selenium-suite/pages/order_page.py +25 -0
  224. package/examples/selenium-suite/pytest.ini +6 -0
  225. package/examples/selenium-suite/requirements.txt +2 -0
  226. package/examples/selenium-suite/tests/test_login.py +10 -0
  227. package/examples/selenium-suite/tests/test_order.py +10 -0
  228. package/package.json +22 -7
  229. package/scripts/generate_skill_examples.py +244 -0
  230. package/scripts/install.ps1 +112 -0
  231. package/scripts/install_global_plugin.ps1 +94 -0
  232. package/scripts/install_global_plugin.sh +49 -0
  233. package/scripts/lint_skills.ps1 +57 -0
  234. package/scripts/package_dist.py +323 -0
  235. package/scripts/setup.sh +59 -0
  236. package/scripts/update_hub.ps1 +19 -0
  237. package/scripts/update_hub.sh +15 -0
  238. package/skills_manifest.json +209 -6
  239. package/synapse.config.json +30 -0
  240. package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +7 -7
  241. package/templates/PROJECT_STATE_TEMPLATE.md +1 -1
  242. package/templates/ai_bridges/CLAUDE.md +33 -16
  243. package/templates/ai_bridges/copilot-instructions.md +14 -21
  244. package/templates/ai_bridges/cursor_synapse_qa.mdc +17 -20
  245. package/templates/ai_bridges/windsurfrules +21 -21
  246. package/templates/ci/github-actions-synapse.yml +71 -4
  247. package/templates/welcome.html.tmpl +1674 -0
  248. package/tools/.infer_baseline.json +1102 -0
  249. package/tools/TOOLS_RUNBOOK.md +92 -130
  250. package/tools/__init__.py +26 -0
  251. package/tools/agent_canary.py +501 -501
  252. package/tools/analyze_project.py +31 -17
  253. package/tools/api_fuzzer.py +278 -6
  254. package/tools/author_integrity.py +6 -6
  255. package/tools/auto_heal.py +1 -1
  256. package/tools/browser_helpers.js +105 -7
  257. package/tools/bug_reporter.py +31 -1
  258. package/tools/bump_version.py +23 -3
  259. package/tools/cache_resolver.py +248 -14
  260. package/tools/check_env.py +55 -14
  261. package/tools/check_version_sync.py +153 -0
  262. package/tools/ci_healing_hook.py +1 -1
  263. package/tools/cli.py +263 -13
  264. package/tools/count_tokens.py +1257 -0
  265. package/tools/dashboard_generator.py +2638 -0
  266. package/tools/db_sandbox.py +198 -20
  267. package/tools/e2e_flow_prober.ps1 +12 -64
  268. package/tools/e2e_flow_prober.py +99 -0
  269. package/tools/e2e_flow_prober.sh +4 -0
  270. package/tools/flaky_detector.py +4 -3
  271. package/tools/generate_allure_report.ps1 +12 -29
  272. package/tools/generate_allure_report.py +77 -0
  273. package/tools/generate_allure_report.sh +4 -0
  274. package/tools/generate_jmx_plan.py +257 -58
  275. package/tools/generate_test_reports.ps1 +12 -134
  276. package/tools/generate_test_reports.py +133 -0
  277. package/tools/generate_test_reports.sh +4 -0
  278. package/tools/gherkin_suite.py +758 -0
  279. package/tools/git_pre_commit_guard.py +12 -6
  280. package/tools/goldfish_review.py +366 -0
  281. package/tools/heal_pr_automator.py +1 -1
  282. package/tools/heuristic_locator_matcher.py +170 -21
  283. package/tools/infer_linter.py +612 -70
  284. package/tools/lint_ai_docs.py +64 -0
  285. package/tools/matrix_sharder.py +1 -1
  286. package/tools/mcp_excel_server.ps1 +12 -250
  287. package/tools/mcp_excel_server.py +249 -0
  288. package/tools/mcp_excel_server.sh +4 -0
  289. package/tools/mcp_server.py +368 -144
  290. package/tools/md_to_xlsx.py +307 -0
  291. package/tools/micro_mutator.py +300 -69
  292. package/tools/omni_transpiler.py +2 -2
  293. package/tools/output_formatter.py +128 -0
  294. package/tools/pinpoint_failure.py +88 -88
  295. package/tools/project_state.py +2 -2
  296. package/tools/repo_map.py +1 -1
  297. package/tools/safety_firewall.py +196 -45
  298. package/tools/setup_project.py +294 -30
  299. package/tools/spec_drift_sentinel.py +439 -0
  300. package/tools/sync_agent_rules.py +248 -0
  301. package/tools/telemetry_correlator.py +45 -0
  302. package/tools/test_impact.py +51 -12
  303. package/tools/tms_sync_bridge.py +21 -5
  304. package/tools/universal_ir.py +3 -0
  305. package/tools/update_sentinel.py +87 -15
  306. package/tools/verify_all.ps1 +13 -0
  307. package/tools/verify_all.py +463 -79
  308. package/tools/visual_diff_canvas.py +1 -0
  309. package/tools/visual_som_helper.py +1 -1
  310. package/tools/watch_mode.py +305 -0
  311. package/tools/welcome_page.py +291 -0
@@ -11,19 +11,29 @@ Features:
11
11
  2. Ephemeral Snapshot & Rapid Restore (< 0.5s):
12
12
  - Takes snapshot of database state before test suite execution.
13
13
  - Restores pristine clean state immediately after test completion.
14
- - Supports SQLite, PostgreSQL schema isolation, MySQL, and Docker/Testcontainers hooks.
14
+ - SQLite implemented natively; other engines via file-copy hooks. Boot
15
+ containers with external `docker`/Testcontainers, then gate readiness
16
+ with the `wait-ready` command (see Wait Strategies below).
15
17
  3. Taint Pollution Auditor:
16
18
  - Automatically detects orphan rows created during test sessions and performs
17
19
  dependency-aware cascading cleanup.
20
+ 4. Container Readiness Wait Strategies (`wait-ready`):
21
+ - port / http / sqlite probes with deadline + backoff, so CI never races
22
+ a not-yet-ready database (concept borrowed from the Testcontainers
23
+ WaitStrategy taxonomy, Apache-2.0, re-implemented stdlib-only).
18
24
  """
19
25
 
20
26
  import os
27
+ import re
21
28
  import sys
22
29
  import time
23
30
  import json
24
31
  import shutil
32
+ import socket
25
33
  import sqlite3
26
34
  import argparse
35
+ import urllib.request
36
+ import urllib.error
27
37
  from typing import Dict, Any, List, Optional, Tuple
28
38
 
29
39
  # Enforce UTF-8 stdout encoding for Windows console compatibility
@@ -51,13 +61,24 @@ class DBSandbox:
51
61
  # -------------------------------------------------------------------------
52
62
  # 1. SNAPSHOT & RAPID RESTORE
53
63
  # -------------------------------------------------------------------------
64
+ def _resolve_safe_snapshot_paths(self, snapshot_id: str) -> Tuple[str, str, str]:
65
+ """Sanitizes snapshot_id and ensures target paths remain inside self.snapshot_dir."""
66
+ clean_id = re.sub(r'[^a-zA-Z0-9_\-]', '', snapshot_id.strip())
67
+ if not clean_id:
68
+ clean_id = "pristine_baseline"
69
+ base_dir = os.path.abspath(self.snapshot_dir)
70
+ snap_path = os.path.abspath(os.path.join(base_dir, f"{clean_id}.db"))
71
+ meta_path = os.path.abspath(os.path.join(base_dir, f"{clean_id}_meta.json"))
72
+ if not snap_path.startswith(base_dir + os.sep) or not meta_path.startswith(base_dir + os.sep):
73
+ raise ValueError(f"Path traversal detected in snapshot_id: '{snapshot_id}'")
74
+ return clean_id, snap_path, meta_path
75
+
54
76
  def create_snapshot(self, snapshot_id: str = "pristine_baseline") -> Dict[str, Any]:
55
77
  """
56
78
  Creates an instantaneous pristine snapshot of the database state.
57
79
  """
58
80
  start_time = time.perf_counter()
59
- snapshot_file = os.path.join(self.snapshot_dir, f"{snapshot_id}.db")
60
- meta_file = os.path.join(self.snapshot_dir, f"{snapshot_id}_meta.json")
81
+ clean_id, snapshot_file, meta_file = self._resolve_safe_snapshot_paths(snapshot_id)
61
82
 
62
83
  if self.db_type == "sqlite":
63
84
  if not self.db_path:
@@ -86,7 +107,7 @@ class DBSandbox:
86
107
  elapsed = round(time.perf_counter() - start_time, 4)
87
108
 
88
109
  metadata = {
89
- "snapshot_id": snapshot_id,
110
+ "snapshot_id": clean_id,
90
111
  "db_type": self.db_type,
91
112
  "source_db": self.db_path,
92
113
  "snapshot_path": snapshot_file,
@@ -104,17 +125,19 @@ class DBSandbox:
104
125
  Restores database to pristine baseline, wiping 100% of test data junk.
105
126
  """
106
127
  start_time = time.perf_counter()
107
- snapshot_file = os.path.join(self.snapshot_dir, f"{snapshot_id}.db")
128
+ try:
129
+ clean_id, snapshot_file, meta_file = self._resolve_safe_snapshot_paths(snapshot_id)
130
+ except ValueError as e:
131
+ return {"status": "ERROR", "message": str(e)}
108
132
 
109
133
  if not os.path.exists(snapshot_file):
110
134
  return {
111
135
  "status": "ERROR",
112
- "message": f"Snapshot '{snapshot_id}' does not exist at {snapshot_file}"
136
+ "message": f"Snapshot '{clean_id}' does not exist at {snapshot_file}"
113
137
  }
114
138
 
115
139
  # Resolve target db path if not explicitly provided
116
140
  if not self.db_path:
117
- meta_file = os.path.join(self.snapshot_dir, f"{snapshot_id}_meta.json")
118
141
  if os.path.exists(meta_file):
119
142
  try:
120
143
  with open(meta_file, "r", encoding="utf-8") as f:
@@ -137,7 +160,7 @@ class DBSandbox:
137
160
  elapsed = round(time.perf_counter() - start_time, 4)
138
161
  return {
139
162
  "status": "RESTORED",
140
- "snapshot_id": snapshot_id,
163
+ "snapshot_id": clean_id,
141
164
  "target_db": self.db_path,
142
165
  "restore_duration_sec": elapsed,
143
166
  "zero_pollution_guaranteed": True
@@ -200,9 +223,20 @@ class DBSandbox:
200
223
  # -------------------------------------------------------------------------
201
224
  # 3. TAINT POLLUTION AUDIT & PURGE
202
225
  # -------------------------------------------------------------------------
203
- def audit_and_purge_pollution(self, known_test_prefixes: Tuple[str, ...] = ("AUTO_", "TEST_", "TEMP_")) -> Dict[str, Any]:
226
+ @staticmethod
227
+ def _escape_sql_like(prefix: str) -> str:
228
+ """Escapes SQL LIKE wildcard characters (_, %) so prefix is matched literally."""
229
+ return prefix.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_')
230
+
231
+ def audit_and_purge_pollution(
232
+ self,
233
+ known_test_prefixes: Tuple[str, ...] = ("AUTO_", "TEST_", "TEMP_"),
234
+ dry_run: bool = False
235
+ ) -> Dict[str, Any]:
204
236
  """
205
237
  Scans all tables for orphan records carrying test prefixes and purges them.
238
+ Properly escapes SQL LIKE wildcards ('_' and '%') with an explicit ESCAPE clause
239
+ to avoid deleting real business data (e.g. 'Automotive', 'Tester', 'Tempest').
206
240
  """
207
241
  if not self.db_path:
208
242
  candidate = os.path.join(self.snapshot_dir, "active_test.db")
@@ -228,32 +262,151 @@ class DBSandbox:
228
262
  purge_log = []
229
263
 
230
264
  for table in tables:
231
- cursor.execute(f"PRAGMA table_info({table});")
265
+ # Validate table identifier to protect against injection
266
+ if not re.match(r'^[a-zA-Z0-9_]+$', table):
267
+ continue
268
+ cursor.execute(f"PRAGMA table_info([{table}]);")
232
269
  columns = [c[1] for c in cursor.fetchall()]
233
270
 
234
271
  # Look for code or name columns
235
- target_cols = [c for c in columns if c.lower() in ("code", "username", "name", "email", "status")]
272
+ target_cols = [c for c in columns if c.lower() in ("code", "username", "name", "email", "status") and re.match(r'^[a-zA-Z0-9_]+$', c)]
236
273
  for col in target_cols:
237
274
  for prefix in known_test_prefixes:
238
- cursor.execute(f"SELECT COUNT(*) FROM {table} WHERE {col} LIKE '{prefix}%'")
275
+ escaped_pattern = self._escape_sql_like(prefix) + "%"
276
+ cursor.execute(
277
+ f"SELECT COUNT(*) FROM [{table}] WHERE [{col}] LIKE ? ESCAPE '\\'",
278
+ (escaped_pattern,)
279
+ )
239
280
  c_count = cursor.fetchone()[0]
240
281
  if c_count > 0:
241
- cursor.execute(f"DELETE FROM {table} WHERE {col} LIKE '{prefix}%'")
282
+ if not dry_run:
283
+ cursor.execute(
284
+ f"DELETE FROM [{table}] WHERE [{col}] LIKE ? ESCAPE '\\'",
285
+ (escaped_pattern,)
286
+ )
242
287
  total_purged += c_count
243
- purge_log.append(f"Table '{table}' ({col}): deleted {c_count} rows matching '{prefix}%'")
288
+ action_str = "previewed" if dry_run else "deleted"
289
+ purge_log.append(f"Table '{table}' ({col}): {action_str} {c_count} rows matching literal prefix '{prefix}'")
244
290
 
245
- conn.commit()
291
+ if not dry_run:
292
+ conn.commit()
246
293
  conn.close()
247
294
 
248
295
  return {
249
- "status": "CLEAN",
296
+ "status": "CLEAN" if not dry_run else "DRY_RUN",
250
297
  "tables_audited": len(tables),
251
298
  "total_purged_rows": total_purged,
252
299
  "purge_details": purge_log,
253
- "zero_pollution_achieved": True
300
+ "zero_pollution_achieved": True,
301
+ "dry_run": dry_run
254
302
  }
255
303
 
256
304
 
305
+ # -------------------------------------------------------------------------
306
+ # 4. CONTAINER READINESS WAIT STRATEGIES
307
+ # (Concept borrowed from the Testcontainers WaitStrategy taxonomy,
308
+ # Apache-2.0 - re-implemented stdlib-only. Boot the container with
309
+ # external `docker`/Testcontainers, then gate on readiness here.)
310
+ # -------------------------------------------------------------------------
311
+
312
+ def _probe_sqlite(target: str) -> Tuple[bool, str]:
313
+ """Ready when the SQLite file opens read-only and answers a pragma."""
314
+ try:
315
+ conn = sqlite3.connect(f"file:{target}?mode=ro", uri=True, timeout=2.0)
316
+ try:
317
+ conn.execute("PRAGMA schema_version").fetchone()
318
+ finally:
319
+ conn.close()
320
+ return True, "sqlite pragma answered"
321
+ except sqlite3.Error as exc:
322
+ return False, f"sqlite probe failed: {exc}"
323
+
324
+
325
+ def _probe_port(target: str) -> Tuple[bool, str]:
326
+ """Ready when a TCP connection to host:port succeeds."""
327
+ m = re.match(r"^(?:[\w.\-]+://)?([\w.\-]+):(\d+)$", target)
328
+ if not m:
329
+ return False, f"invalid host:port target '{target}'"
330
+ host, port = m.group(1), int(m.group(2))
331
+ try:
332
+ with socket.create_connection((host, port), timeout=2.0):
333
+ return True, f"tcp {host}:{port} accepted"
334
+ except OSError as exc:
335
+ return False, f"tcp {host}:{port} refused: {exc}"
336
+
337
+
338
+ def _probe_http(target: str) -> Tuple[bool, str]:
339
+ """Ready when the endpoint answers with any non-5xx status (an auth wall
340
+ such as 401/403 still proves the service is up)."""
341
+ try:
342
+ req = urllib.request.Request(target, method="GET")
343
+ with urllib.request.urlopen(req, timeout=2.0) as resp:
344
+ return True, f"http {resp.getcode()}"
345
+ except urllib.error.HTTPError as exc:
346
+ if exc.code < 500:
347
+ return True, f"http {exc.code} (service reachable)"
348
+ return False, f"http {exc.code} server error"
349
+ except (urllib.error.URLError, OSError) as exc:
350
+ return False, f"http probe failed: {exc}"
351
+
352
+
353
+ def infer_wait_strategy(target: str) -> str:
354
+ """Auto-detects the wait strategy from the target shape."""
355
+ lowered = target.lower()
356
+ if lowered.startswith("http://") or lowered.startswith("https://"):
357
+ return "http"
358
+ if lowered.endswith((".db", ".sqlite", ".sqlite3")) or os.path.exists(target):
359
+ return "sqlite"
360
+ if re.match(r"^[\w.\-]+:\d+$", target):
361
+ return "port"
362
+ return "port"
363
+
364
+
365
+ def wait_for_ready(target: str, strategy: str = "auto", timeout_sec: float = 30.0) -> Dict[str, Any]:
366
+ """Polls a readiness probe until it succeeds or the deadline expires.
367
+
368
+ Returns a structured result; exit codes are enforced by the CLI
369
+ (0 = ready, 1 = timeout) so CI pipelines can gate on database health
370
+ before dispatching suites.
371
+ """
372
+ if strategy == "auto":
373
+ strategy = infer_wait_strategy(target)
374
+ probes = {"sqlite": _probe_sqlite, "port": _probe_port, "http": _probe_http}
375
+ probe = probes.get(strategy)
376
+ if probe is None:
377
+ return {"target": target, "strategy": strategy, "ready": False,
378
+ "elapsed_ms": 0, "attempts": 0, "error": f"unknown strategy '{strategy}'"}
379
+
380
+ start = time.perf_counter()
381
+ attempts = 0
382
+ backoff = 0.25
383
+ last_error = ""
384
+ while time.perf_counter() - start < timeout_sec:
385
+ attempts += 1
386
+ ok, detail = probe(target)
387
+ if ok:
388
+ return {
389
+ "target": target,
390
+ "strategy": strategy,
391
+ "ready": True,
392
+ "elapsed_ms": round((time.perf_counter() - start) * 1000, 1),
393
+ "attempts": attempts,
394
+ "error": None,
395
+ }
396
+ last_error = detail
397
+ time.sleep(backoff)
398
+ backoff = min(backoff * 2, 2.0)
399
+
400
+ return {
401
+ "target": target,
402
+ "strategy": strategy,
403
+ "ready": False,
404
+ "elapsed_ms": round((time.perf_counter() - start) * 1000, 1),
405
+ "attempts": attempts,
406
+ "error": last_error or "timeout expired before readiness",
407
+ }
408
+
409
+
257
410
  def main():
258
411
  parser = argparse.ArgumentParser(description="Zero-Pollution Database Sandbox & Ephemeral Teardown")
259
412
  subparsers = parser.add_subparsers(dest="command", help="Sandbox command")
@@ -279,10 +432,33 @@ def main():
279
432
  # Command: audit
280
433
  aud_p = subparsers.add_parser("audit", help="Audit orphan test junk and purge")
281
434
  aud_p.add_argument("--db-path", default="", help="Path to database file")
435
+ aud_p.add_argument("--dry-run", action="store_true", help="Preview test records to purge without deleting")
282
436
  aud_p.add_argument("--json", action="store_true", help="Output JSON format")
283
437
 
438
+ # Command: wait-ready (Testcontainers-style readiness gate)
439
+ wait_p = subparsers.add_parser("wait-ready", help="Wait until a database/container is ready (port|http|sqlite probe)")
440
+ wait_p.add_argument("target", help="URL (http(s)://), host:port, or SQLite file path")
441
+ wait_p.add_argument("--strategy", choices=["auto", "port", "http", "sqlite"], default="auto",
442
+ help="Readiness probe strategy (default: auto-detect)")
443
+ wait_p.add_argument("--timeout", type=float, default=30.0, help="Deadline in seconds (default: 30)")
444
+ wait_p.add_argument("--json", action="store_true", help="Output JSON format")
445
+
284
446
  args = parser.parse_args()
285
447
 
448
+ if args.command == "wait-ready":
449
+ res = wait_for_ready(args.target, strategy=args.strategy, timeout_sec=args.timeout)
450
+ if args.json:
451
+ print(json.dumps(res, indent=2))
452
+ else:
453
+ if res["ready"]:
454
+ print(f"✅ [READY] {res['target']} via {res['strategy']} probe "
455
+ f"({res['elapsed_ms']}ms, {res['attempts']} attempt(s))")
456
+ else:
457
+ print(f"❌ [NOT READY] {res['target']} via {res['strategy']} probe "
458
+ f"after {res['elapsed_ms']}ms / {res['attempts']} attempt(s)")
459
+ print(f" Last error: {res['error']}")
460
+ return 0 if res["ready"] else 1
461
+
286
462
  sandbox = DBSandbox(db_path=args.db_path)
287
463
 
288
464
  if args.command == "snapshot":
@@ -322,14 +498,16 @@ def main():
322
498
  return 0
323
499
 
324
500
  elif args.command == "audit":
325
- res = sandbox.audit_and_purge_pollution()
501
+ res = sandbox.audit_and_purge_pollution(dry_run=args.dry_run)
326
502
  if args.json:
327
503
  print(json.dumps(res, indent=2))
328
504
  else:
329
- print(f"🧹 [TAINT POLLUTION AUDIT] Purged {res['total_purged_rows']} orphan test records.")
505
+ action_label = "Previewed" if args.dry_run else "Purged"
506
+ print(f"🧹 [TAINT POLLUTION AUDIT] {action_label} {res['total_purged_rows']} orphan test records.")
330
507
  for detail in res.get("purge_details", []):
331
508
  print(f" - {detail}")
332
- print(" ✅ Zero Pollution Achieved.")
509
+ if not args.dry_run:
510
+ print(" ✅ Zero Pollution Achieved.")
333
511
  return 0
334
512
 
335
513
  else:
@@ -1,65 +1,13 @@
1
- param(
2
- [string]$Mode = "katalon" # "katalon" or "playwright"
3
- )
4
-
5
- Write-Host "==========================================================" -ForegroundColor Cyan
6
- Write-Host " ENTERPRISE AUTOMATION - E2E FLOW PROBER & XPATH CRAWLER" -ForegroundColor Cyan
7
- Write-Host "==========================================================" -ForegroundColor Cyan
8
-
9
- $projectDir = Split-Path -Parent $PSScriptRoot
10
-
11
- if ($Mode -eq "playwright") {
12
- $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
13
- if (-not $pythonCmd) { $pythonCmd = Get-Command py -ErrorAction SilentlyContinue }
14
- if ($pythonCmd) {
15
- Write-Host "[+] Khởi chạy crawler qua Playwright (real_e2e_runner.py)..." -ForegroundColor Green
16
- & $pythonCmd "$projectDir\real_e2e_runner.py" --probe
17
- exit 0
18
- } else {
19
- Write-Host "[!] Không tìm thấy Python trên máy host." -ForegroundColor Yellow
20
- Write-Host "[+] Tự động chuyển sang chế độ Katalon Native Runner..." -ForegroundColor Cyan
21
- $Mode = "katalon"
22
- }
1
+ # Enterprise Automation - E2E Flow Prober Wrapper
2
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
3
+ $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
4
+ if (-not $pythonCmd) { $pythonCmd = Get-Command py -ErrorAction SilentlyContinue }
5
+ if (-not $pythonCmd) { $pythonCmd = Get-Command python3 -ErrorAction SilentlyContinue }
6
+
7
+ if ($pythonCmd) {
8
+ & $pythonCmd.Source "$scriptDir\e2e_flow_prober.py" $args
9
+ exit $LASTEXITCODE
10
+ } else {
11
+ Write-Error "Python 3 is required to run e2e_flow_prober.py."
12
+ exit 1
23
13
  }
24
-
25
- if ($Mode -eq "katalon") {
26
- Write-Host "[+] Khởi chạy crawler qua Katalon Native Test Suite (PROBE_E2E_Flow)..." -ForegroundColor Green
27
-
28
- $katalonExec = $null
29
- $possiblePaths = @(
30
- $env:KATALON_HOME,
31
- "C:\Katalon_Studio_Engine\katalonc.exe",
32
- "C:\Katalon_Studio\katalon.exe"
33
- )
34
- foreach ($p in $possiblePaths) {
35
- if ($p -and (Test-Path $p)) {
36
- $katalonExec = $p
37
- break
38
- }
39
- }
40
-
41
- if ($katalonExec) {
42
- Write-Host "[+] Sử dụng Katalon Engine tại: $katalonExec" -ForegroundColor Cyan
43
- $prjPath = "$projectDir\project.prj"
44
- $suiteRelPath = "Test Suites/PROBE_E2E_Flow"
45
-
46
- $argsList = @(
47
- "-noSplash",
48
- "-runMode=console",
49
- "-projectPath=`"$prjPath`"",
50
- "-retry=0",
51
- "-testSuitePath=`"$suiteRelPath`"",
52
- "-browserType=`"Chrome`""
53
- )
54
- if ($env:KATALON_API_KEY) {
55
- $argsList += "-apiKey=`"$($env:KATALON_API_KEY)`""
56
- }
57
-
58
- & $katalonExec $argsList
59
- } else {
60
- Write-Host "`n[!] Để chạy bộ dò luồng tự động:" -ForegroundColor Yellow
61
- Write-Host " 1. Mở Katalon Studio -> Open Project tại thư mục dự án này" -ForegroundColor White
62
- Write-Host " 2. Chọn Test Suites -> 'PROBE_E2E_Flow' -> Bấm nút Run (Chrome)" -ForegroundColor White
63
- Write-Host " 3. Hoặc chạy lệnh trong Docker: docker-compose up" -ForegroundColor White
64
- }
65
- }
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise E2E Flow Prober & XPath Crawler Dispatcher (Cross-Platform)
4
+ ----------------------------------------------------------------------
5
+ Dispatches autonomous flow probing across Katalon Studio and Playwright.
6
+ Replaces PowerShell-only script with a zero-dependency, cross-platform engine.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import shutil
12
+ import argparse
13
+ import subprocess
14
+
15
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
16
+ PROJECT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
17
+
18
+
19
+ def find_katalon_binary():
20
+ """Locates the katalonc binary across Windows, macOS, and Linux."""
21
+ from_path = shutil.which("katalonc") or shutil.which("katalon")
22
+ if from_path:
23
+ return from_path
24
+
25
+ env_home = os.environ.get("KATALON_HOME")
26
+ if env_home:
27
+ for candidate in ["katalonc", "katalonc.exe", "katalon", "katalon.exe"]:
28
+ p = os.path.join(env_home, candidate)
29
+ if os.path.isfile(p):
30
+ return p
31
+
32
+ standard_paths = [
33
+ "C:\\Katalon_Studio_Engine\\katalonc.exe",
34
+ "C:\\Katalon_Studio\\katalon.exe",
35
+ "/Applications/Katalon Studio Engine.app/Contents/MacOS/katalonc",
36
+ "/opt/katalon/katalonc",
37
+ "/opt/homebrew/bin/katalonc",
38
+ ]
39
+ for p in standard_paths:
40
+ if os.path.isfile(p):
41
+ return p
42
+ return None
43
+
44
+
45
+ def run_prober(mode="playwright", suite_path="Test Suites/PROBE_E2E_Flow"):
46
+ """Dispatches flow prober according to selected mode."""
47
+ print("==========================================================")
48
+ print(" ENTERPRISE AUTOMATION - E2E FLOW PROBER & XPATH CRAWLER")
49
+ print("==========================================================")
50
+
51
+ if mode == "playwright":
52
+ runner_path = os.path.join(PROJECT_ROOT, "real_e2e_runner.py")
53
+ if os.path.exists(runner_path):
54
+ print(f"[+] Launching Playwright E2E Crawler ({runner_path})...")
55
+ return subprocess.run([sys.executable, runner_path, "--probe"]).returncode
56
+ else:
57
+ print("[!] real_e2e_runner.py not found in project root.")
58
+ print("[+] Falling back to Playwright test runner...")
59
+ npx = shutil.which("npx")
60
+ if npx:
61
+ return subprocess.run([npx, "playwright", "test", "--grep", "@probe"]).returncode
62
+ print("[ERROR] Neither real_e2e_runner.py nor npx found.")
63
+ return 1
64
+
65
+ elif mode == "katalon":
66
+ katalon_bin = find_katalon_binary()
67
+ if not katalon_bin:
68
+ print("[!] Katalon Runtime Engine (katalonc) not found on PATH or standard locations.")
69
+ print(" Please set KATALON_HOME or ensure katalonc is installed.")
70
+ return 1
71
+
72
+ print(f"[+] Launching Katalon Suite Prober via {katalon_bin}...")
73
+ cmd = [
74
+ katalon_bin,
75
+ "-noSplash",
76
+ "-runMode=console",
77
+ f"-testSuitePath={suite_path}",
78
+ "-browserType=Chrome (headless)"
79
+ ]
80
+ return subprocess.run(cmd).returncode
81
+
82
+ else:
83
+ print(f"[ERROR] Unsupported mode '{mode}'. Choose 'playwright' or 'katalon'.")
84
+ return 2
85
+
86
+
87
+ def main():
88
+ parser = argparse.ArgumentParser(description="E2E Flow Prober and DOM Crawler Dispatcher")
89
+ parser.add_argument("--mode", choices=["playwright", "katalon"], default="playwright",
90
+ help="Execution engine for prober (default: playwright)")
91
+ parser.add_argument("--suite", default="Test Suites/PROBE_E2E_Flow",
92
+ help="Katalon test suite path (if mode=katalon)")
93
+ args = parser.parse_args()
94
+
95
+ sys.exit(run_prober(mode=args.mode, suite_path=args.suite))
96
+
97
+
98
+ if __name__ == "__main__":
99
+ main()
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # Enterprise E2E Flow Prober Wrapper
3
+ set -e
4
+ python3 "$(dirname "$0")/e2e_flow_prober.py" "$@"
@@ -20,6 +20,7 @@ import sys
20
20
  import glob
21
21
  import json
22
22
  import time
23
+ import shlex
23
24
  import argparse
24
25
  import subprocess
25
26
  import xml.etree.ElementTree as ET
@@ -56,12 +57,12 @@ RESET = "\033[0m"
56
57
 
57
58
 
58
59
  def run_command(cmd: str, timeout: int = 300) -> Tuple[int, str, str, float]:
59
- """Executes a shell command and returns (returncode, stdout, stderr, elapsed_ms)."""
60
+ """Executes a command safely without shell=True and returns (returncode, stdout, stderr, elapsed_ms)."""
60
61
  start = time.time()
61
62
  try:
63
+ args = shlex.split(cmd, posix=sys.platform != "win32")
62
64
  res = subprocess.run(
63
- cmd,
64
- shell=True,
65
+ args,
65
66
  stdout=subprocess.PIPE,
66
67
  stderr=subprocess.PIPE,
67
68
  text=True,
@@ -1,30 +1,13 @@
1
- Write-Host "==========================================================" -ForegroundColor Cyan
2
- Write-Host " ENTERPRISE AUTOMATION - ALLURE REPORT GENERATOR" -ForegroundColor Cyan
3
- Write-Host "==========================================================" -ForegroundColor Cyan
4
-
5
- $projectDir = Split-Path -Parent $PSScriptRoot
6
- $resultsDir = Join-Path $projectDir "allure-results"
7
- $reportDir = Join-Path $projectDir "allure-report"
8
-
9
- if (-not (Test-Path $resultsDir)) {
10
- Write-Host "[!] Không tìm thấy thư mục: $resultsDir" -ForegroundColor Yellow
11
- Write-Host "[!] Hãy thực thi Test Suite trước để sinh dữ liệu kết quả." -ForegroundColor Yellow
12
- exit 0
13
- }
14
-
15
- # Kiểm tra Allure CLI
16
- $allureCmd = Get-Command allure -ErrorAction SilentlyContinue
17
-
18
- if ($allureCmd) {
19
- Write-Host "[+] Đang xuất báo cáo Allure HTML..." -ForegroundColor Green
20
- allure generate $resultsDir -o $reportDir --clean
21
- Write-Host "[+] Xuất báo cáo hoàn tất tại: $reportDir" -ForegroundColor Green
22
- Write-Host "[+] Đang mở báo cáo trên trình duyệt..." -ForegroundColor Cyan
23
- allure open $reportDir
1
+ # Enterprise Automation - Allure Report Generator Wrapper
2
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
3
+ $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
4
+ if (-not $pythonCmd) { $pythonCmd = Get-Command py -ErrorAction SilentlyContinue }
5
+ if (-not $pythonCmd) { $pythonCmd = Get-Command python3 -ErrorAction SilentlyContinue }
6
+
7
+ if ($pythonCmd) {
8
+ & $pythonCmd.Source "$scriptDir\generate_allure_report.py" $args
9
+ exit $LASTEXITCODE
24
10
  } else {
25
- Write-Host "[!] Không tìm thấy công cụ Allure CLI trong hệ thống." -ForegroundColor Yellow
26
- Write-Host "[*] Hướng dẫn cài đặt nhanh:" -ForegroundColor White
27
- Write-Host " Cách 1: npm install -g allure-commandline" -ForegroundColor Gray
28
- Write-Host " Cách 2: scoop install allure" -ForegroundColor Gray
29
- Write-Host " Cách 3: choco install allure-commandline" -ForegroundColor Gray
30
- }
11
+ Write-Error "Python 3 is required to run generate_allure_report.py."
12
+ exit 1
13
+ }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enterprise Allure HTML Report Generator (Cross-Platform)
4
+ ---------------------------------------------------------
5
+ Dispatches Allure report generation and local web serving across Windows, macOS, and Linux.
6
+ Replaces legacy PowerShell-only scripts with a zero-dependency, cross-platform engine.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import shutil
12
+ import subprocess
13
+ import argparse
14
+
15
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
16
+ PROJECT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
17
+
18
+
19
+ def generate_allure_report(results_dir=None, report_dir=None, clean=True, open_browser=False):
20
+ """Generates an Allure HTML report from raw result files."""
21
+ results_path = results_dir or os.path.join(PROJECT_ROOT, "allure-results")
22
+ report_path = report_dir or os.path.join(PROJECT_ROOT, "allure-report")
23
+
24
+ if not os.path.exists(results_path):
25
+ print(f"[!] Results directory not found: {results_path}")
26
+ print("[!] Please execute your test suite first to generate raw test result files.")
27
+ return 0
28
+
29
+ allure_cmd = shutil.which("allure")
30
+ if not allure_cmd:
31
+ print("[!] Allure CLI executable was not found on PATH.")
32
+ print("[*] Quick installation instructions:")
33
+ print(" npm install -g allure-commandline")
34
+ print(" brew install allure (macOS)")
35
+ print(" scoop install allure (Windows)")
36
+ return 1
37
+
38
+ cmd = [allure_cmd, "generate", results_path, "-o", report_path]
39
+ if clean:
40
+ cmd.append("--clean")
41
+
42
+ print(f"[+] Generating Allure HTML report from '{results_path}' to '{report_path}'...")
43
+ try:
44
+ ret = subprocess.run(cmd, check=True)
45
+ print(f"[+] Report generated successfully at: {report_path}")
46
+ except subprocess.CalledProcessError as e:
47
+ print(f"[ERROR] Allure report generation failed with exit code {e.returncode}")
48
+ return e.returncode
49
+
50
+ if open_browser:
51
+ print("[+] Launching Allure report server...")
52
+ try:
53
+ subprocess.run([allure_cmd, "open", report_path])
54
+ except KeyboardInterrupt:
55
+ print("\n[+] Allure server closed.")
56
+
57
+ return 0
58
+
59
+
60
+ def main():
61
+ parser = argparse.ArgumentParser(description="Generate and serve Allure HTML test reports")
62
+ parser.add_argument("--results-dir", default=None, help="Directory containing raw allure-results")
63
+ parser.add_argument("--report-dir", default=None, help="Output directory for generated HTML report")
64
+ parser.add_argument("--no-clean", action="store_true", help="Do not clean output report directory before generation")
65
+ parser.add_argument("--open", action="store_true", help="Automatically open report in browser via local server")
66
+ args = parser.parse_args()
67
+
68
+ sys.exit(generate_allure_report(
69
+ results_dir=args.results_dir,
70
+ report_dir=args.report_dir,
71
+ clean=not args.no_clean,
72
+ open_browser=args.open
73
+ ))
74
+
75
+
76
+ if __name__ == "__main__":
77
+ main()
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # Enterprise Allure Report Generator Wrapper
3
+ set -e
4
+ python3 "$(dirname "$0")/generate_allure_report.py" "$@"