testa-agent 0.1.0__tar.gz
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.
- testa_agent-0.1.0/PKG-INFO +11 -0
- testa_agent-0.1.0/pyproject.toml +41 -0
- testa_agent-0.1.0/setup.cfg +4 -0
- testa_agent-0.1.0/testa_agent/__init__.py +3 -0
- testa_agent-0.1.0/testa_agent/cli.py +108 -0
- testa_agent-0.1.0/testa_agent/config.py +64 -0
- testa_agent-0.1.0/testa_agent/executor.py +987 -0
- testa_agent-0.1.0/testa_agent/main.py +198 -0
- testa_agent-0.1.0/testa_agent/parser.py +302 -0
- testa_agent-0.1.0/testa_agent/poller.py +70 -0
- testa_agent-0.1.0/testa_agent/reporter.py +86 -0
- testa_agent-0.1.0/testa_agent/runner.py +127 -0
- testa_agent-0.1.0/testa_agent/vision.py +800 -0
- testa_agent-0.1.0/testa_agent.egg-info/PKG-INFO +11 -0
- testa_agent-0.1.0/testa_agent.egg-info/SOURCES.txt +17 -0
- testa_agent-0.1.0/testa_agent.egg-info/dependency_links.txt +1 -0
- testa_agent-0.1.0/testa_agent.egg-info/entry_points.txt +2 -0
- testa_agent-0.1.0/testa_agent.egg-info/requires.txt +7 -0
- testa_agent-0.1.0/testa_agent.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: testa-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Testa - AI-powered post-deploy testing
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: httpx>=0.26.0
|
|
7
|
+
Requires-Dist: playwright>=1.41.2
|
|
8
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
9
|
+
Requires-Dist: click>=8.1.0
|
|
10
|
+
Provides-Extra: vision
|
|
11
|
+
Requires-Dist: anthropic>=0.40.0; extra == "vision"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "testa-agent"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Testa - AI-powered post-deploy testing"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"httpx>=0.26.0",
|
|
12
|
+
"playwright>=1.41.2",
|
|
13
|
+
"pyyaml>=6.0.1",
|
|
14
|
+
"click>=8.1.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
vision = ["anthropic>=0.40.0"]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
testa = "testa_agent.cli:main"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
include = ["testa_agent*"]
|
|
25
|
+
|
|
26
|
+
[tool.mypy]
|
|
27
|
+
python_version = "3.11"
|
|
28
|
+
warn_return_any = false
|
|
29
|
+
warn_unused_ignores = false
|
|
30
|
+
disallow_untyped_defs = false
|
|
31
|
+
ignore_missing_imports = true
|
|
32
|
+
check_untyped_defs = true
|
|
33
|
+
disable_error_code = ["import-untyped"]
|
|
34
|
+
|
|
35
|
+
[[tool.mypy.overrides]]
|
|
36
|
+
module = ["testa_agent.executor", "testa_agent.vision"]
|
|
37
|
+
disable_error_code = ["union-attr", "return-value"]
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
target-version = "py311"
|
|
41
|
+
line-length = 100
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""CLI entry point for the Testa agent."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
@click.version_option(package_name="testa")
|
|
13
|
+
def main():
|
|
14
|
+
"""Testa - AI-powered post-deploy testing."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@main.command()
|
|
19
|
+
@click.option(
|
|
20
|
+
"--config",
|
|
21
|
+
"config_path",
|
|
22
|
+
default="./testa.yaml",
|
|
23
|
+
help="Path to testa.yaml config file.",
|
|
24
|
+
type=click.Path(exists=True),
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--api-url",
|
|
28
|
+
default=None,
|
|
29
|
+
help="Testa API URL (or set TESTA_API_URL).",
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--license-key",
|
|
33
|
+
default=None,
|
|
34
|
+
help="License key (or set TESTA_LICENSE_KEY).",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--headless/--no-headless",
|
|
38
|
+
default=True,
|
|
39
|
+
help="Run browser in headless mode.",
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--timeout",
|
|
43
|
+
default=30000,
|
|
44
|
+
help="Default step timeout in ms.",
|
|
45
|
+
type=int,
|
|
46
|
+
)
|
|
47
|
+
def run(config_path, api_url, license_key, headless, timeout):
|
|
48
|
+
"""Run tests once and exit (CI mode).
|
|
49
|
+
|
|
50
|
+
Reads tests from a local testa.yaml, executes them, reports results
|
|
51
|
+
to the Testa API, and exits with code 0 (all pass) or 1 (any fail).
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
testa run --config ./testa.yaml
|
|
55
|
+
"""
|
|
56
|
+
# Configure logging for CI output
|
|
57
|
+
logging.basicConfig(
|
|
58
|
+
level=logging.INFO,
|
|
59
|
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
60
|
+
handlers=[logging.StreamHandler(sys.stdout)],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Resolve config from args or env vars
|
|
64
|
+
resolved_key = license_key or os.getenv("TESTA_LICENSE_KEY") or os.getenv("LICENSE_KEY")
|
|
65
|
+
if not resolved_key:
|
|
66
|
+
click.echo(
|
|
67
|
+
"Error: License key required. Use --license-key or set TESTA_LICENSE_KEY.", err=True
|
|
68
|
+
)
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
resolved_url: str = (
|
|
72
|
+
api_url or os.getenv("TESTA_API_URL") or os.getenv("API_URL") or "http://localhost:8000"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
from testa_agent.runner import ci_run
|
|
76
|
+
|
|
77
|
+
exit_code = asyncio.run(
|
|
78
|
+
ci_run(
|
|
79
|
+
config_path=config_path,
|
|
80
|
+
api_url=resolved_url,
|
|
81
|
+
license_key=resolved_key,
|
|
82
|
+
headless=headless,
|
|
83
|
+
default_timeout_ms=timeout,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
sys.exit(exit_code)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@main.command()
|
|
90
|
+
def serve():
|
|
91
|
+
"""Start the agent in polling mode (self-hosted).
|
|
92
|
+
|
|
93
|
+
Registers with the Testa API and continuously polls for work.
|
|
94
|
+
This is the existing agent behavior for self-hosted deployments.
|
|
95
|
+
|
|
96
|
+
Environment variables:
|
|
97
|
+
LICENSE_KEY: Required license key
|
|
98
|
+
API_URL: Testa API URL (default: http://localhost:8000)
|
|
99
|
+
CONFIG_PATH: Path to testa.yaml (default: /config/testa.yaml)
|
|
100
|
+
ANTHROPIC_API_KEY: For direct vision calls
|
|
101
|
+
"""
|
|
102
|
+
from testa_agent.main import run
|
|
103
|
+
|
|
104
|
+
run()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class SlackConfig:
|
|
8
|
+
"""Slack notification configuration."""
|
|
9
|
+
|
|
10
|
+
webhook_url: Optional[str] = None
|
|
11
|
+
bot_token: Optional[str] = None # For screenshot uploads
|
|
12
|
+
channel_id: Optional[str] = None # Required if bot_token is set
|
|
13
|
+
channel_name: Optional[str] = None
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def is_configured(self) -> bool:
|
|
17
|
+
"""Check if Slack is configured."""
|
|
18
|
+
return bool(self.webhook_url)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def supports_screenshots(self) -> bool:
|
|
22
|
+
"""Check if screenshot uploads are supported."""
|
|
23
|
+
return bool(self.bot_token and self.channel_id)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class AgentConfig:
|
|
28
|
+
"""Agent configuration loaded from environment variables."""
|
|
29
|
+
|
|
30
|
+
license_key: str
|
|
31
|
+
api_url: str
|
|
32
|
+
config_path: str
|
|
33
|
+
poll_interval_seconds: int = 30
|
|
34
|
+
slack: Optional[SlackConfig] = None
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_env(cls) -> "AgentConfig":
|
|
38
|
+
"""Load configuration from environment variables."""
|
|
39
|
+
license_key = os.getenv("LICENSE_KEY")
|
|
40
|
+
if not license_key:
|
|
41
|
+
raise ValueError("LICENSE_KEY environment variable is required")
|
|
42
|
+
|
|
43
|
+
api_url = os.getenv("API_URL", "http://localhost:8000")
|
|
44
|
+
config_path = os.getenv("CONFIG_PATH", "/config/testa.yaml")
|
|
45
|
+
poll_interval = int(os.getenv("POLL_INTERVAL_SECONDS", "30"))
|
|
46
|
+
|
|
47
|
+
# Load Slack config from environment
|
|
48
|
+
slack_config = None
|
|
49
|
+
slack_webhook = os.getenv("SLACK_WEBHOOK_URL")
|
|
50
|
+
if slack_webhook:
|
|
51
|
+
slack_config = SlackConfig(
|
|
52
|
+
webhook_url=slack_webhook,
|
|
53
|
+
bot_token=os.getenv("SLACK_BOT_TOKEN"),
|
|
54
|
+
channel_id=os.getenv("SLACK_CHANNEL_ID"),
|
|
55
|
+
channel_name=os.getenv("SLACK_CHANNEL_NAME"),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return cls(
|
|
59
|
+
license_key=license_key,
|
|
60
|
+
api_url=api_url,
|
|
61
|
+
config_path=config_path,
|
|
62
|
+
poll_interval_seconds=poll_interval,
|
|
63
|
+
slack=slack_config,
|
|
64
|
+
)
|