Skip to content

Commit

Permalink
bug fixes, interaction logs creation with trivial selectors, adding s…
Browse files Browse the repository at this point in the history
…creenshots with boundingbox and helpbox (#43)

### Summary 📝
bug fixes, interaction logs creation with trivial selectors, adding screenshots with boundingbox and helpbox

### Details
1. interaction logs
2. Browser channels and versions support
3. clipboard tool, paste from clipboard
4. upload tool fix.
5. bounding box based images as proofs.

### Bugfixes 🐛
Broken azure llm integration fix.
  • Loading branch information
shriyanshagnihotri authored Feb 24, 2025
1 parent b0c8c68 commit c745d8b
Show file tree
Hide file tree
Showing 17 changed files with 2,077 additions and 388 deletions.
152 changes: 131 additions & 21 deletions testzeus_hercules/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@
import argparse
import json
import os
from typing import Optional
from typing import Optional, Literal

from dotenv import load_dotenv
from testzeus_hercules.utils.logger import logger
from testzeus_hercules.utils.timestamp_helper import get_timestamp_str

TS = get_timestamp_str()

# Add browser channel types
BROWSER_CHANNELS = Literal[
"chrome",
"chrome-beta",
"chrome-dev",
"chrome-canary",
"msedge",
"msedge-beta",
"msedge-dev",
"msedge-canary",
"firefox",
"firefox-beta",
"firefox-dev-edition",
"firefox-nightly",
]


class BaseConfigManager:
"""
Expand Down Expand Up @@ -64,11 +80,15 @@ def __init__(self, config_dict: dict, ignore_env: bool = False):
# -------------------------------------------------------------------------

@classmethod
def from_dict(cls, config_dict: dict, ignore_env: bool = False) -> "BaseConfigManager":
def from_dict(
cls, config_dict: dict, ignore_env: bool = False
) -> "BaseConfigManager":
return cls(config_dict, ignore_env=ignore_env)

@classmethod
def from_json(cls, json_file_path: str, ignore_env: bool = False) -> "BaseConfigManager":
def from_json(
cls, json_file_path: str, ignore_env: bool = False
) -> "BaseConfigManager":
with open(json_file_path, "r", encoding="utf-8") as f:
config_dict = json.load(f)
return cls(config_dict, ignore_env=ignore_env)
Expand All @@ -82,8 +102,12 @@ def _parse_arguments(self) -> None:
Parse Hercules-specific command-line arguments
and place them into the environment for consistency.
"""
parser = argparse.ArgumentParser(description="Hercules: The World's First Open-Source AI Agent for End-to-End Testing")
parser.add_argument("--input-file", type=str, help="Path to the input file.", required=False)
parser = argparse.ArgumentParser(
description="Hercules: The World's First Open-Source AI Agent for End-to-End Testing"
)
parser.add_argument(
"--input-file", type=str, help="Path to the input file.", required=False
)
parser.add_argument(
"--output-path",
type=str,
Expand Down Expand Up @@ -126,6 +150,24 @@ def _parse_arguments(self) -> None:
help="Reuse existing vector DB instead of creating fresh one",
required=False,
)
parser.add_argument(
"--browser-channel",
type=str,
help="Browser channel to use (e.g., chrome-beta, firefox-nightly)",
required=False,
)
parser.add_argument(
"--browser-path",
type=str,
help="Custom path to browser executable",
required=False,
)
parser.add_argument(
"--browser-version",
type=str,
help="Specific browser version to use (e.g., '114', '115.0.1', 'latest')",
required=False,
)

# Parse known args; ignore unknown if you have other custom arguments
args, _ = parser.parse_known_args()
Expand All @@ -146,6 +188,12 @@ def _parse_arguments(self) -> None:
os.environ["EXECUTE_BULK"] = "true"
if args.reuse_vector_db:
os.environ["REUSE_VECTOR_DB"] = "true"
if args.browser_channel:
os.environ["BROWSER_CHANNEL"] = args.browser_channel
if args.browser_path:
os.environ["BROWSER_PATH"] = args.browser_path
if args.browser_version:
os.environ["BROWSER_VERSION"] = args.browser_version

def _merge_from_env(self) -> None:
"""
Expand Down Expand Up @@ -189,10 +237,14 @@ def _merge_from_env(self) -> None:
"EXECUTE_BULK",
"USE_DYNAMIC_LTM",
"REUSE_VECTOR_DB",
"ENABLE_BROWSER_LOGS",
"BROWSER_CHANNEL",
"BROWSER_VERSION",
"BROWSER_PATH",
"ENABLE_PLAYWRIGHT_TRACING",
"ENABLE_BOUNDING_BOX_SCREENSHOTS",
]

relevant_keys.append("ENABLE_PLAYWRIGHT_TRACING")

for key in relevant_keys:
if key in os.environ:
self._config[key] = os.environ[key]
Expand All @@ -211,13 +263,22 @@ def _check_llm_config(self) -> None:
llm_model_name = self._config.get("LLM_MODEL_NAME")
llm_model_api_key = self._config.get("LLM_MODEL_API_KEY")
agents_llm_config_file = self._config.get("AGENTS_LLM_CONFIG_FILE")
agents_llm_config_file_ref_key = self._config.get("AGENTS_LLM_CONFIG_FILE_REF_KEY")
agents_llm_config_file_ref_key = self._config.get(
"AGENTS_LLM_CONFIG_FILE_REF_KEY"
)

if (llm_model_name and llm_model_api_key) and (agents_llm_config_file or agents_llm_config_file_ref_key):
logger.error("Provide either LLM_MODEL_NAME and LLM_MODEL_API_KEY together, " "or AGENTS_LLM_CONFIG_FILE and AGENTS_LLM_CONFIG_FILE_REF_KEY together, not both.")
if (llm_model_name and llm_model_api_key) and (
agents_llm_config_file or agents_llm_config_file_ref_key
):
logger.error(
"Provide either LLM_MODEL_NAME and LLM_MODEL_API_KEY together, "
"or AGENTS_LLM_CONFIG_FILE and AGENTS_LLM_CONFIG_FILE_REF_KEY together, not both."
)
exit(1)

if (not llm_model_name or not llm_model_api_key) and (not agents_llm_config_file or not agents_llm_config_file_ref_key):
if (not llm_model_name or not llm_model_api_key) and (
not agents_llm_config_file or not agents_llm_config_file_ref_key
):
logger.error(
"Either LLM_MODEL_NAME and LLM_MODEL_API_KEY must be set together, "
"or AGENTS_LLM_CONFIG_FILE and AGENTS_LLM_CONFIG_FILE_REF_KEY must be set together. "
Expand All @@ -241,12 +302,24 @@ def _finalize_defaults(self) -> None:
"INPUT_GHERKIN_FILE_PATH",
os.path.join(project_source_root, "input/test.feature"),
)
self._config.setdefault("JUNIT_XML_BASE_PATH", os.path.join(project_source_root, "output"))
self._config.setdefault("TEST_DATA_PATH", os.path.join(project_source_root, "test_data"))
self._config.setdefault("SCREEN_SHOT_PATH", os.path.join(project_source_root, "proofs"))
self._config.setdefault("PROJECT_TEMP_PATH", os.path.join(project_source_root, "temp"))
self._config.setdefault("SOURCE_LOG_FOLDER_PATH", os.path.join(project_source_root, "log_files"))
self._config.setdefault("TMP_GHERKIN_PATH", os.path.join(project_source_root, "gherkin_files"))
self._config.setdefault(
"JUNIT_XML_BASE_PATH", os.path.join(project_source_root, "output")
)
self._config.setdefault(
"TEST_DATA_PATH", os.path.join(project_source_root, "test_data")
)
self._config.setdefault(
"SCREEN_SHOT_PATH", os.path.join(project_source_root, "proofs")
)
self._config.setdefault(
"PROJECT_TEMP_PATH", os.path.join(project_source_root, "temp")
)
self._config.setdefault(
"SOURCE_LOG_FOLDER_PATH", os.path.join(project_source_root, "log_files")
)
self._config.setdefault(
"TMP_GHERKIN_PATH", os.path.join(project_source_root, "gherkin_files")
)

# Extra environment defaults from original code
if "HF_HOME" not in self._config:
Expand All @@ -267,7 +340,7 @@ def _finalize_defaults(self) -> None:
self._config.setdefault("HEADLESS", "true")
self._config.setdefault("RECORD_VIDEO", "true")
self._config.setdefault("TAKE_SCREENSHOTS", "true")
self._config.setdefault("BROWSER_TYPE", "chromium")

self._config.setdefault("CAPTURE_NETWORK", "true")
self._config.setdefault("DONT_CLOSE_BROWSER", "false")
self._config.setdefault("GEO_PROVIDER", None)
Expand All @@ -277,9 +350,17 @@ def _finalize_defaults(self) -> None:
self._config.setdefault("ENABLE_PLAYWRIGHT_TRACING", "false")
self._config.setdefault("REUSE_VECTOR_DB", "false")
self._config.setdefault("USE_DYNAMIC_LTM", "false")
self._config.setdefault("ENABLE_BROWSER_LOGS", "false")
self._config.setdefault("ENABLE_BOUNDING_BOX_SCREENSHOTS", "false")

self._config.setdefault("PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION", "python")

# Browser configuration
self._config.setdefault("BROWSER_TYPE", "chromium")
self._config.setdefault("BROWSER_CHANNEL", None) # Default to stable channel
self._config.setdefault("BROWSER_PATH", None) # Default to system browser
self._config.setdefault("BROWSER_VERSION", None) # Default to latest version

if self._config["MODE"] == "debug":
self.timestamp = "0"

Expand Down Expand Up @@ -357,6 +438,29 @@ def should_use_dynamic_ltm(self) -> bool:
"""Return whether to use dynamic LTM or static LTM."""
return self._config["USE_DYNAMIC_LTM"].lower().strip() == "true"

def should_enable_browser_logs(self) -> bool:
"""Check if browser logging should be enabled"""
return self._config.get("ENABLE_BROWSER_LOGS", "false").lower() == "true"

def get_browser_channel(self) -> Optional[BROWSER_CHANNELS]:
"""Get the configured browser channel (e.g., chrome-beta, firefox-nightly)"""
return self._config.get("BROWSER_CHANNEL")

def get_browser_path(self) -> Optional[str]:
"""Get the custom browser executable path if configured"""
return self._config.get("BROWSER_PATH")

def get_browser_version(self) -> Optional[str]:
"""Get the configured browser version (e.g., '114', '115.0.1', 'latest')"""
return self._config.get("BROWSER_VERSION")

def should_take_bounding_box_screenshots(self) -> bool:
"""Check if bounding box screenshots should be enabled"""
return (
self._config.get("ENABLE_BOUNDING_BOX_SCREENSHOTS", "false").lower()
== "true"
)

# -------------------------------------------------------------------------
# Directory creation logic (mirroring your original code)
# -------------------------------------------------------------------------
Expand Down Expand Up @@ -547,8 +651,12 @@ def get_global_conf() -> SingletonConfigManager:
return SingletonConfigManager.instance()


def set_global_conf(config_dict: Optional[dict] = None, ignore_env: bool = False, override: bool = False) -> SingletonConfigManager:
return SingletonConfigManager.instance(config_dict, ignore_env=ignore_env, override=override)
def set_global_conf(
config_dict: Optional[dict] = None, ignore_env: bool = False, override: bool = False
) -> SingletonConfigManager:
return SingletonConfigManager.instance(
config_dict, ignore_env=ignore_env, override=override
)


set_global_conf(
Expand All @@ -562,5 +670,7 @@ def set_global_conf(config_dict: Optional[dict] = None, ignore_env: bool = False
from testzeus_hercules.telemetry import EventData, EventType, add_event

logger.info("[Singleton] MODE: %s", get_global_conf().get_mode())
logger.info("[Singleton] Project Source Root: %s", get_global_conf().get_project_source_root())
logger.info(
"[Singleton] Project Source Root: %s", get_global_conf().get_project_source_root()
)
# Send final telemetryCONF.send_config_telemetry()
Loading

0 comments on commit c745d8b

Please sign in to comment.