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.
- package/.agents/agents/failure-analyst.md +11 -0
- package/.agents/agents/live-explorer.md +28 -0
- package/.agents/agents/orchestrator.md +20 -4
- package/.agents/agents/pom-crafter.md +12 -0
- package/.agents/agents/quality-gatekeeper.md +19 -4
- package/.agents/agents/test-runner.md +11 -0
- package/.agents/rules/communication-style.md +40 -0
- package/.agents/rules/context-engineering.md +23 -0
- package/.agents/rules/git-push-protocol.md +10 -16
- package/.agents/rules/graphify.md +14 -14
- package/.agents/rules/mandatory-playwright-live.md +29 -29
- package/.agents/rules/mobile_appium_rules.md +132 -0
- package/.agents/rules/performance_testing_rules.md +74 -0
- package/.agents/rules/systematic-debugging.md +6 -6
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +64 -215
- package/.agents/skills/agent-orchestration-multi-agent-optimize/examples/swarm_workload_config.json +12 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +3 -3
- package/.agents/skills/antigravity-test-sentinel/examples/dod_gate_policy.json +11 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +1 -1
- package/.agents/skills/api-network-mocking-interception/examples/network_route_stub.json +9 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +9 -9
- package/.agents/skills/api-spec-to-e2e-matrix/examples/sample_openapi.json +19 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +35 -412
- package/.agents/skills/architecture-decision-records/examples/0001-record-architecture-decisions.md +12 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +1 -1
- package/.agents/skills/auth-session-and-cookie-manager/examples/storage_state_sample.json +11 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +80 -186
- package/.agents/skills/backend-load-and-stress-patterns/examples/k6_load_test_sample.js +13 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/bats-testing-patterns/examples/cli_runner_test.bats +6 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -614
- package/.agents/skills/clean-pom-crafter/SKILL.md +21 -3
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -28
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -138
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +2 -2
- package/.agents/skills/code-refactoring-refactor-clean/examples/refactored_page_sample.groovy +9 -0
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +5 -5
- package/.agents/skills/db-state-verifier-and-seeder/examples/db_fixture_seed.json +12 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +1 -1
- package/.agents/skills/e2e-testing-patterns/examples/playwright_sharded_config.ts +12 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -522
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +17 -3
- package/.agents/skills/failure-autopsy-and-healing/examples/sample_junit_failure.xml +8 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -64
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -56
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -98
- package/.agents/skills/file-and-report-validator/SKILL.md +1 -1
- package/.agents/skills/file-and-report-validator/examples/sample_export_schema.json +14 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +3 -3
- package/.agents/skills/flaky-test-sentinel-and-quarantine/examples/quarantine_registry.json +11 -0
- package/.agents/skills/gherkin-to-suite/SKILL.md +103 -0
- package/.agents/skills/gherkin-to-suite/examples/checkout.feature +39 -0
- package/.agents/skills/gherkin-to-suite/examples/suite_report_sample.json +334 -0
- package/.agents/skills/github-actions-templates/SKILL.md +47 -304
- package/.agents/skills/github-actions-templates/examples/matrix_test_pipeline.yml +8 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +6 -6
- package/.agents/skills/graphify-impact-analyzer/examples/dependency_blast_radius.json +8 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +2 -2
- package/.agents/skills/katalon-cli-runner/examples/headless_kre_exec.sh +3 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -209
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -155
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -67
- package/.agents/skills/lam-script-generator/SKILL.md +42 -1
- package/.agents/skills/lam-script-generator/examples/user_story_sample.md +8 -0
- package/.agents/skills/lean-code-ladder/SKILL.md +83 -0
- package/.agents/skills/lean-code-ladder/examples/ladder_walkthrough.md +37 -0
- package/.agents/skills/meta-prompt-crafter/SKILL.md +107 -0
- package/.agents/skills/meta-prompt-crafter/examples/qa_prompt_template.md +35 -0
- package/.agents/skills/persistent-planning-files/SKILL.md +108 -0
- package/.agents/skills/persistent-planning-files/examples/task_plan_example.md +56 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +49 -1
- package/.agents/skills/playwright-live-interaction/examples/live_probe_script.js +5 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -160
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -75
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -388
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -134
- package/.agents/skills/playwright-webapp-testing/SKILL.md +93 -0
- package/.agents/skills/playwright-webapp-testing/examples/server_lifecycle_playwright.py +63 -0
- package/.agents/skills/rbt-manual-testing/SKILL.md +150 -0
- package/.agents/skills/rbt-manual-testing/examples/checkout_rbt_manual_suite.md +25 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +54 -135
- package/.agents/skills/security-dast-sentinel/examples/dast_scan_config.json +10 -0
- package/.agents/skills/self-healing-locator/SKILL.md +24 -10
- package/.agents/skills/self-healing-locator/examples/aom_diff_sample.json +16 -0
- package/.agents/skills/skillspector-security-guard/SKILL.md +32 -90
- package/.agents/skills/skillspector-security-guard/examples/safe_mcp_config.json +12 -0
- package/.agents/skills/surgical-coding-principles/SKILL.md +89 -0
- package/.agents/skills/surgical-coding-principles/examples/surgical_vs_scope_creep.md +46 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +3 -3
- package/.agents/skills/synthetic-test-data-generator/examples/idempotent_data_recipe.json +9 -0
- package/.agents/skills/systematic-debugging/SKILL.md +100 -0
- package/.agents/skills/systematic-debugging/examples/four_phase_case.md +44 -0
- package/.agents/skills/test-driven-development/SKILL.md +114 -0
- package/.agents/skills/test-driven-development/examples/red_green_refactor_qa.md +78 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +1 -1
- package/.agents/skills/token-and-context-optimizer/examples/distilled_log_sample.txt +3 -0
- package/.agents/skills/verification-before-completion/SKILL.md +100 -0
- package/.agents/skills/verification-before-completion/examples/evidence_log_example.md +46 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +21 -1
- package/.agents/skills/visual-regression-sentinel/examples/visual_mask_rules.json +9 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +3 -3
- package/.agents/skills/wcag-audit-patterns/examples/wcag_compliance_checklist.json +10 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -541
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +1 -1
- package/.agents/skills/web-vitals-and-performance-profiler/examples/cwv_budget_policy.json +6 -0
- package/.agents/templates/completion_report.md +57 -57
- package/.agents/workflows/analyze_requirement_document.md +18 -0
- package/.agents/workflows/export_testcases_excel.md +32 -0
- package/.agents/workflows/generate_combinatorial_test_data.md +28 -0
- package/.agents/workflows/generate_cross_module_test_plan.md +37 -0
- package/.agents/workflows/generate_manual_testcases_rbt.md +56 -0
- package/.agents/workflows/generate_requirements_from_website.md +25 -0
- package/.agents/workflows/generate_testcases_from_requirements.md +21 -0
- package/.agents/workflows/graphify.md +10 -10
- package/.agents/workflows/verify.md +2 -2
- package/.cursor/rules/language.mdc +7 -7
- package/.cursor/rules/synapse-qa.mdc +23 -0
- package/.cursorrules +7 -0
- package/.pre-commit-hooks.yaml +14 -0
- package/.windsurfrules +7 -0
- package/AGENTS.md +54 -15
- package/CLAUDE.md +35 -12
- package/LICENSE +30 -0
- package/NOTICE +180 -0
- package/README.en.md +325 -0
- package/README.md +247 -516
- package/action.yml +51 -0
- package/benchmarks/qa_eval/README.md +71 -0
- package/benchmarks/qa_eval/build_dataset.py +677 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-06_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-07_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-08_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-09_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/api_contract_drift/qa-challenge-10_api_contract_drift.json +22 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-21_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-22_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-23_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-24_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/fake_pass_tautology/qa-challenge-25_fake_pass_tautology.json +21 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-01_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-02_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-03_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-04_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/locator_drift/qa-challenge-05_locator_drift.json +23 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-16_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-17_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-18_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-19_stale_and_mutation.json +22 -0
- package/benchmarks/qa_eval/dataset/stale_and_mutation/qa-challenge-20_stale_and_mutation.json +23 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-11_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-12_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-13_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-14_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/dataset/timing_and_race/qa-challenge-15_timing_and_race.json +22 -0
- package/benchmarks/qa_eval/eval_runner.py +418 -0
- package/benchmarks/qa_eval/reports/AB_METHODOLOGY_REPORT.md +83 -0
- package/benchmarks/qa_eval/reports/BENCHMARK_REPORT.md +69 -0
- package/benchmarks/qa_eval/reports/eval_results.json +105 -0
- package/bin/agent-qa.js +10 -3
- package/docs/ARCHITECTURE.md +107 -0
- package/docs/CI_CD_INTEGRATION.md +94 -0
- package/docs/CLI_REFERENCE.md +157 -0
- package/docs/CONTRIBUTING.md +91 -0
- package/docs/PLUGIN_DEVELOPMENT_GUIDE.md +126 -0
- package/docs/TOKEN_QUOTA_PLAYBOOK.md +41 -0
- package/docs/adopted-ecosystem-tools.md +87 -0
- package/docs/schemas/synapse.config.schema.json +64 -0
- package/examples/cypress-ecommerce-suite/README.md +45 -0
- package/examples/cypress-ecommerce-suite/cypress/e2e/checkout.cy.js +17 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/BasePage.js +29 -0
- package/examples/cypress-ecommerce-suite/cypress/pages/CartPage.js +51 -0
- package/examples/cypress-ecommerce-suite/cypress.config.js +13 -0
- package/examples/cypress-ecommerce-suite/package.json +13 -0
- package/examples/katalon-suite/Keywords/pages/BasePage.groovy +19 -0
- package/examples/katalon-suite/Keywords/pages/DashboardPage.groovy +29 -0
- package/examples/katalon-suite/Keywords/pages/LoginPage.groovy +45 -0
- package/examples/katalon-suite/Profiles/default.glbl +12 -0
- package/examples/katalon-suite/README.md +18 -0
- package/examples/katalon-suite/Scripts/Auth/TC_LoginSuccess/Script1234567890.groovy +12 -0
- package/examples/katalon-suite/Scripts/Dashboard/TC_ViewSummary/Script1234567891.groovy +15 -0
- package/examples/katalon-suite/katalon_suite.prj +21 -0
- package/examples/playwright-crm-suite/README.md +40 -0
- package/examples/playwright-crm-suite/package.json +14 -0
- package/examples/playwright-crm-suite/pages/BasePage.ts +37 -0
- package/examples/playwright-crm-suite/pages/CustomerPage.ts +57 -0
- package/examples/playwright-crm-suite/pages/LoginPage.ts +32 -0
- package/examples/playwright-crm-suite/playwright.config.ts +27 -0
- package/examples/playwright-crm-suite/tests/auth.spec.ts +22 -0
- package/examples/playwright-crm-suite/tests/customers.spec.ts +33 -0
- package/examples/plugins/sample-custom-plugin/README.md +14 -0
- package/examples/plugins/sample-custom-plugin/plugin.json +16 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/SKILL.md +29 -0
- package/examples/plugins/sample-custom-plugin/skills/custom-security-headers-audit/examples/audit_fixture.json +8 -0
- package/examples/plugins/sample-custom-plugin/tools/custom_security_headers.py +125 -0
- package/examples/practices/README.md +22 -0
- package/examples/practices/requirements/crm/requirements_contracts.md +292 -0
- package/examples/practices/requirements/crm/requirements_customers.md +462 -0
- package/examples/practices/requirements/crm/requirements_login.md +202 -0
- package/examples/practices/requirements/crm/requirements_projects.md +396 -0
- package/examples/practices/requirements/crm/requirements_tasks.md +343 -0
- package/examples/practices/requirements/ecommerce/requirements_category_brand.txt +402 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/2026-04-03_04-28-27.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-1/KAN-1_requirement.md +52 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-19.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/2026-04-03_04-28-57.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-4/KAN-4_requirement.md +119 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-5_overview.md +9 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-08.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/2026-04-03_05-58-23.png +0 -0
- package/examples/practices/requirements/jira/KAN-5/KAN-6/KAN-6_requirement.md +32 -0
- package/examples/practices/testcases/api/book_management_api_test_cases.md +144 -0
- package/examples/practices/testcases/api/user_management_api_test_cases.md +139 -0
- package/examples/practices/testcases/crm/test_cases_crm_login.md +148 -0
- package/examples/practices/testcases/crm/test_cases_customers.md +156 -0
- package/examples/pytest-api-suite/README.md +45 -0
- package/examples/pytest-api-suite/conftest.py +31 -0
- package/examples/pytest-api-suite/pages/user_api_client.py +36 -0
- package/examples/pytest-api-suite/requirements.txt +2 -0
- package/examples/pytest-api-suite/tests/test_user_api.py +44 -0
- package/examples/selenium-suite/README.md +18 -0
- package/examples/selenium-suite/pages/base_page.py +27 -0
- package/examples/selenium-suite/pages/login_page.py +29 -0
- package/examples/selenium-suite/pages/order_page.py +25 -0
- package/examples/selenium-suite/pytest.ini +6 -0
- package/examples/selenium-suite/requirements.txt +2 -0
- package/examples/selenium-suite/tests/test_login.py +10 -0
- package/examples/selenium-suite/tests/test_order.py +10 -0
- package/package.json +22 -7
- package/scripts/generate_skill_examples.py +244 -0
- package/scripts/install.ps1 +112 -0
- package/scripts/install_global_plugin.ps1 +94 -0
- package/scripts/install_global_plugin.sh +49 -0
- package/scripts/lint_skills.ps1 +57 -0
- package/scripts/package_dist.py +323 -0
- package/scripts/setup.sh +59 -0
- package/scripts/update_hub.ps1 +19 -0
- package/scripts/update_hub.sh +15 -0
- package/skills_manifest.json +209 -6
- package/synapse.config.json +30 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +7 -7
- package/templates/PROJECT_STATE_TEMPLATE.md +1 -1
- package/templates/ai_bridges/CLAUDE.md +33 -16
- package/templates/ai_bridges/copilot-instructions.md +14 -21
- package/templates/ai_bridges/cursor_synapse_qa.mdc +17 -20
- package/templates/ai_bridges/windsurfrules +21 -21
- package/templates/ci/github-actions-synapse.yml +71 -4
- package/templates/welcome.html.tmpl +1674 -0
- package/tools/.infer_baseline.json +1102 -0
- package/tools/TOOLS_RUNBOOK.md +92 -130
- package/tools/__init__.py +26 -0
- package/tools/agent_canary.py +501 -501
- package/tools/analyze_project.py +31 -17
- package/tools/api_fuzzer.py +278 -6
- package/tools/author_integrity.py +6 -6
- package/tools/auto_heal.py +1 -1
- package/tools/browser_helpers.js +105 -7
- package/tools/bug_reporter.py +31 -1
- package/tools/bump_version.py +23 -3
- package/tools/cache_resolver.py +248 -14
- package/tools/check_env.py +55 -14
- package/tools/check_version_sync.py +153 -0
- package/tools/ci_healing_hook.py +1 -1
- package/tools/cli.py +263 -13
- package/tools/count_tokens.py +1257 -0
- package/tools/dashboard_generator.py +2638 -0
- package/tools/db_sandbox.py +198 -20
- package/tools/e2e_flow_prober.ps1 +12 -64
- package/tools/e2e_flow_prober.py +99 -0
- package/tools/e2e_flow_prober.sh +4 -0
- package/tools/flaky_detector.py +4 -3
- package/tools/generate_allure_report.ps1 +12 -29
- package/tools/generate_allure_report.py +77 -0
- package/tools/generate_allure_report.sh +4 -0
- package/tools/generate_jmx_plan.py +257 -58
- package/tools/generate_test_reports.ps1 +12 -134
- package/tools/generate_test_reports.py +133 -0
- package/tools/generate_test_reports.sh +4 -0
- package/tools/gherkin_suite.py +758 -0
- package/tools/git_pre_commit_guard.py +12 -6
- package/tools/goldfish_review.py +366 -0
- package/tools/heal_pr_automator.py +1 -1
- package/tools/heuristic_locator_matcher.py +170 -21
- package/tools/infer_linter.py +612 -70
- package/tools/lint_ai_docs.py +64 -0
- package/tools/matrix_sharder.py +1 -1
- package/tools/mcp_excel_server.ps1 +12 -250
- package/tools/mcp_excel_server.py +249 -0
- package/tools/mcp_excel_server.sh +4 -0
- package/tools/mcp_server.py +368 -144
- package/tools/md_to_xlsx.py +307 -0
- package/tools/micro_mutator.py +300 -69
- package/tools/omni_transpiler.py +2 -2
- package/tools/output_formatter.py +128 -0
- package/tools/pinpoint_failure.py +88 -88
- package/tools/project_state.py +2 -2
- package/tools/repo_map.py +1 -1
- package/tools/safety_firewall.py +196 -45
- package/tools/setup_project.py +294 -30
- package/tools/spec_drift_sentinel.py +439 -0
- package/tools/sync_agent_rules.py +248 -0
- package/tools/telemetry_correlator.py +45 -0
- package/tools/test_impact.py +51 -12
- package/tools/tms_sync_bridge.py +21 -5
- package/tools/universal_ir.py +3 -0
- package/tools/update_sentinel.py +87 -15
- package/tools/verify_all.ps1 +13 -0
- package/tools/verify_all.py +463 -79
- package/tools/visual_diff_canvas.py +1 -0
- package/tools/visual_som_helper.py +1 -1
- package/tools/watch_mode.py +305 -0
- package/tools/welcome_page.py +291 -0
package/tools/db_sandbox.py
CHANGED
|
@@ -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
|
-
-
|
|
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 =
|
|
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":
|
|
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
|
-
|
|
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 '{
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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()
|
package/tools/flaky_detector.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
64
|
-
shell=True,
|
|
65
|
+
args,
|
|
65
66
|
stdout=subprocess.PIPE,
|
|
66
67
|
stderr=subprocess.PIPE,
|
|
67
68
|
text=True,
|
|
@@ -1,30 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
$
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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-
|
|
26
|
-
|
|
27
|
-
|
|
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()
|