fusiontest 0.2.7__tar.gz → 0.2.8__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.
Files changed (44) hide show
  1. fusiontest-0.2.8/PKG-INFO +201 -0
  2. fusiontest-0.2.8/README.md +127 -0
  3. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/cli.py +15 -9
  4. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/runner.py +0 -26
  5. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/desktop/playwright_adapter.py +0 -45
  6. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/reporting/reporter.py +7 -0
  7. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/training/__init__.py +5 -0
  8. fusiontest-0.2.8/fusiontest.egg-info/PKG-INFO +201 -0
  9. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest.egg-info/SOURCES.txt +0 -2
  10. {fusiontest-0.2.7 → fusiontest-0.2.8}/pyproject.toml +1 -1
  11. fusiontest-0.2.7/PKG-INFO +0 -266
  12. fusiontest-0.2.7/README.md +0 -192
  13. fusiontest-0.2.7/fusiontest/recording/__init__.py +0 -19
  14. fusiontest-0.2.7/fusiontest/recording/recorder.py +0 -309
  15. fusiontest-0.2.7/fusiontest.egg-info/PKG-INFO +0 -266
  16. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/__init__.py +0 -0
  17. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/__init__.py +0 -0
  18. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/action_model.py +0 -0
  19. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/goal_verifier.py +0 -0
  20. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/netguard.py +0 -0
  21. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/replay.py +0 -0
  22. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/screen_parser.py +0 -0
  23. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/secrets.py +0 -0
  24. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/core/tokens.py +0 -0
  25. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/desktop/__init__.py +0 -0
  26. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/desktop/macos_adapter.py +0 -0
  27. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/desktop/windows_adapter.py +0 -0
  28. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/discovery/__init__.py +0 -0
  29. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/discovery/goal_generator.py +0 -0
  30. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/guardrails/__init__.py +0 -0
  31. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/guardrails/engine.py +0 -0
  32. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/mobile/__init__.py +0 -0
  33. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/mobile/android_adapter.py +0 -0
  34. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/mobile/ios_adapter.py +0 -0
  35. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/reporting/__init__.py +0 -0
  36. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/training/data_collector.py +0 -0
  37. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/training/dataset_builder.py +0 -0
  38. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest/training/trainer.py +0 -0
  39. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest.egg-info/dependency_links.txt +0 -0
  40. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest.egg-info/entry_points.txt +0 -0
  41. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest.egg-info/not-zip-safe +0 -0
  42. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest.egg-info/requires.txt +0 -0
  43. {fusiontest-0.2.7 → fusiontest-0.2.8}/fusiontest.egg-info/top_level.txt +0 -0
  44. {fusiontest-0.2.7 → fusiontest-0.2.8}/setup.cfg +0 -0
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: fusiontest
3
+ Version: 0.2.8
4
+ Summary: AI-powered UI testing for mobile and desktop — by FusionLeap.io
5
+ Author-email: FusionLeap <hello@fusionleap.io>
6
+ License: MIT
7
+ Project-URL: Homepage, https://fusionleap.io
8
+ Project-URL: Repository, https://github.com/mohigo/fusion-test
9
+ Project-URL: Issues, https://github.com/mohigo/fusion-test/issues
10
+ Keywords: testing,AI,mobile,desktop,LLM,automation,QA
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Testing
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: click>=8.1
21
+ Requires-Dist: pyyaml>=6.0
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: python-dotenv>=1.0
24
+ Requires-Dist: rich>=13.0
25
+ Requires-Dist: pydantic>=2.0
26
+ Requires-Dist: openai>=1.58
27
+ Provides-Extra: mobile
28
+ Requires-Dist: Appium-Python-Client>=3.1; extra == "mobile"
29
+ Requires-Dist: selenium>=4.18; extra == "mobile"
30
+ Provides-Extra: desktop
31
+ Requires-Dist: playwright>=1.43; extra == "desktop"
32
+ Provides-Extra: windows
33
+ Requires-Dist: Appium-Python-Client>=3.1; extra == "windows"
34
+ Requires-Dist: selenium>=4.18; extra == "windows"
35
+ Provides-Extra: mpnet
36
+ Requires-Dist: sentence-transformers>=2.7; extra == "mpnet"
37
+ Requires-Dist: torch>=2.2; extra == "mpnet"
38
+ Provides-Extra: api
39
+ Requires-Dist: fastapi>=0.111; extra == "api"
40
+ Requires-Dist: uvicorn[standard]>=0.29; extra == "api"
41
+ Requires-Dist: pydantic[email]>=2.0; extra == "api"
42
+ Requires-Dist: python-jose[cryptography]>=3.3; extra == "api"
43
+ Requires-Dist: cryptography>=42; extra == "api"
44
+ Requires-Dist: passlib[bcrypt]>=1.7; extra == "api"
45
+ Requires-Dist: psycopg2-binary>=2.9; extra == "api"
46
+ Provides-Extra: training
47
+ Requires-Dist: sentence-transformers>=2.7; extra == "training"
48
+ Requires-Dist: torch>=2.2; extra == "training"
49
+ Requires-Dist: datasets>=2.19; extra == "training"
50
+ Requires-Dist: accelerate>=0.30; extra == "training"
51
+ Requires-Dist: scikit-learn>=1.4; extra == "training"
52
+ Requires-Dist: pandas>=2.2; extra == "training"
53
+ Requires-Dist: numpy>=1.26; extra == "training"
54
+ Requires-Dist: tqdm>=4.66; extra == "training"
55
+ Requires-Dist: wandb>=0.17; extra == "training"
56
+ Provides-Extra: openai
57
+ Requires-Dist: openai>=1.58; extra == "openai"
58
+ Provides-Extra: groq
59
+ Requires-Dist: openai>=1.58; extra == "groq"
60
+ Provides-Extra: claude
61
+ Requires-Dist: anthropic>=0.25; extra == "claude"
62
+ Provides-Extra: gemini
63
+ Requires-Dist: google-genai>=1.0; extra == "gemini"
64
+ Provides-Extra: dev
65
+ Requires-Dist: pytest>=8.0; extra == "dev"
66
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
67
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
68
+ Requires-Dist: ruff>=0.4; extra == "dev"
69
+ Requires-Dist: mypy>=1.10; extra == "dev"
70
+ Requires-Dist: pre-commit>=3.7; extra == "dev"
71
+ Requires-Dist: fusiontest[desktop,mobile,openai]; extra == "dev"
72
+ Provides-Extra: all
73
+ Requires-Dist: fusiontest[api,claude,desktop,gemini,groq,mobile,mpnet,openai,training,windows]; extra == "all"
74
+
75
+ # 🧪 FusionTest
76
+
77
+ > AI-powered UI testing for web, desktop and mobile — built by [FusionLeap.io](https://fusionleap.io)
78
+
79
+ FusionTest runs UI tests written as plain-English goals. An LLM reads the
80
+ screen and decides what to do, a verifier confirms each goal against what's
81
+ actually on screen, and goals that pass are recorded so later runs replay them
82
+ without calling the AI at all.
83
+
84
+ ---
85
+
86
+ ## ✨ Features
87
+
88
+ | Feature | Notes |
89
+ |---|---|
90
+ | Natural-language goals | `"Open the Pricing page and verify all three plans are shown"` |
91
+ | Goal discovery | Crawls a site and proposes goals grounded in its real content |
92
+ | Record & replay | Passing goals replay with no LLM calls; the AI repairs them when the page changes |
93
+ | Honest results | Capacity or infrastructure problems are reported as **errors**, not test failures |
94
+ | Multi-provider LLMs | Groq, Claude, Gemini, OpenAI or local Ollama, with automatic fallback |
95
+ | Safe secrets | `{{PASSWORD}}` placeholders — the AI never sees values, which are only typed on allowed sites |
96
+ | Guardrails | Invalid-action checks, loop detection, backtracking, no-progress detection |
97
+ | Reporting | JSON reports, dashboard uploads, Slack, GitHub Actions annotations, webhooks |
98
+ | Platforms | Web (Playwright), Electron, Android & iOS (Appium), Windows, macOS |
99
+
100
+ ---
101
+
102
+ ## 🏗 Architecture
103
+
104
+ ```
105
+ fusiontest/
106
+ ├── core/ # Runner, LLM action model + fallback chain, goal verifier,
107
+ │ # screen parser, secrets, replay, SSRF guard (netguard)
108
+ ├── desktop/ # Playwright (web/Electron), Windows, macOS adapters
109
+ ├── mobile/ # Android (UIAutomator2) and iOS (XCUITest) adapters via Appium
110
+ ├── discovery/ # Goal discovery from a site's pages
111
+ ├── guardrails/ # Action validation, loop detection, backtracking
112
+ ├── reporting/ # JSON reports, Slack, GitHub annotations
113
+ └── training/ # Experimental MPNet fine-tuning (not wired into runs)
114
+ api/ # SaaS backend (FastAPI + PostgreSQL) — see docs/api.md
115
+ dashboard/ # React dashboard
116
+ website/ # Marketing site (Next.js)
117
+ ```
118
+
119
+ Design decisions are recorded in [docs/adr](docs/adr) — start with
120
+ [ADR-007](docs/adr/ADR-007-llm-capacity-and-execution.md).
121
+
122
+ ---
123
+
124
+ ## 🚀 Quickstart
125
+
126
+ ```bash
127
+ pip install "fusiontest[desktop]"
128
+ playwright install chromium
129
+
130
+ export GROQ_API_KEY=gsk_... # or ANTHROPIC_API_KEY / GEMINI_API_KEY / OPENAI_API_KEY
131
+
132
+ fusiontest run --url https://example.com \
133
+ --goal "Verify the page heading says Example Domain" --headless
134
+ ```
135
+
136
+ Set `FUSIONTEST_API_KEY` (from the dashboard's API Keys page) to upload each
137
+ run to your FusionTest dashboard.
138
+
139
+ Exit codes: `0` all goals passed · `1` a goal failed · `2` some goals couldn't
140
+ be evaluated (LLM capacity or infrastructure) — not a test failure.
141
+
142
+ > **Full guide:** [docs/getting-started.md](docs/getting-started.md) — platforms,
143
+ > goal files, configuration, secrets, replay, dashboard and CI.
144
+
145
+ ---
146
+
147
+ ## 📝 Writing goals
148
+
149
+ ```yaml
150
+ # tests/goals/smoke.yaml
151
+ name: Marketing site smoke test
152
+ platform: playwright
153
+ url: https://example.com
154
+ goals:
155
+ - "Open the Pricing page and verify three plan tiers are shown"
156
+ - "Open the Contact page, submit the form empty, and verify required-field errors appear"
157
+ - name: Log in
158
+ goal: "Log in with {{EMAIL}} and {{PASSWORD}} and verify the dashboard loads"
159
+ ```
160
+
161
+ ```bash
162
+ fusiontest run --file tests/goals/smoke.yaml --headless
163
+ ```
164
+
165
+ Tips: say what to verify, not just where to go; one outcome per goal; don't
166
+ submit real forms on production sites.
167
+
168
+ ---
169
+
170
+ ## 🛠 Configuration
171
+
172
+ `fusiontest init` creates `fusiontest.config.yaml`:
173
+
174
+ ```yaml
175
+ backend: groq # unset = auto-detect from API keys
176
+ guardrails:
177
+ loop_detection_window: 5
178
+ max_retries: 3
179
+ replay:
180
+ enabled: true
181
+ trace_dir: .fusiontest/traces
182
+ reverify_every: 10
183
+ ```
184
+
185
+ ---
186
+
187
+ ## 🔌 CI/CD
188
+
189
+ Run `fusiontest run --headless` in any CI system and cache
190
+ `.fusiontest/traces` so runs can replay. Guides for GitHub Actions, Bitrise,
191
+ Fastlane and Jenkins: [docs/ci-integrations.md](docs/ci-integrations.md).
192
+
193
+ ---
194
+
195
+ ## 🤝 Contributing
196
+
197
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [ROADMAP.md](ROADMAP.md).
198
+
199
+ ## 📄 License
200
+
201
+ MIT © [FusionLeap.io](https://fusionleap.io)
@@ -0,0 +1,127 @@
1
+ # 🧪 FusionTest
2
+
3
+ > AI-powered UI testing for web, desktop and mobile — built by [FusionLeap.io](https://fusionleap.io)
4
+
5
+ FusionTest runs UI tests written as plain-English goals. An LLM reads the
6
+ screen and decides what to do, a verifier confirms each goal against what's
7
+ actually on screen, and goals that pass are recorded so later runs replay them
8
+ without calling the AI at all.
9
+
10
+ ---
11
+
12
+ ## ✨ Features
13
+
14
+ | Feature | Notes |
15
+ |---|---|
16
+ | Natural-language goals | `"Open the Pricing page and verify all three plans are shown"` |
17
+ | Goal discovery | Crawls a site and proposes goals grounded in its real content |
18
+ | Record & replay | Passing goals replay with no LLM calls; the AI repairs them when the page changes |
19
+ | Honest results | Capacity or infrastructure problems are reported as **errors**, not test failures |
20
+ | Multi-provider LLMs | Groq, Claude, Gemini, OpenAI or local Ollama, with automatic fallback |
21
+ | Safe secrets | `{{PASSWORD}}` placeholders — the AI never sees values, which are only typed on allowed sites |
22
+ | Guardrails | Invalid-action checks, loop detection, backtracking, no-progress detection |
23
+ | Reporting | JSON reports, dashboard uploads, Slack, GitHub Actions annotations, webhooks |
24
+ | Platforms | Web (Playwright), Electron, Android & iOS (Appium), Windows, macOS |
25
+
26
+ ---
27
+
28
+ ## 🏗 Architecture
29
+
30
+ ```
31
+ fusiontest/
32
+ ├── core/ # Runner, LLM action model + fallback chain, goal verifier,
33
+ │ # screen parser, secrets, replay, SSRF guard (netguard)
34
+ ├── desktop/ # Playwright (web/Electron), Windows, macOS adapters
35
+ ├── mobile/ # Android (UIAutomator2) and iOS (XCUITest) adapters via Appium
36
+ ├── discovery/ # Goal discovery from a site's pages
37
+ ├── guardrails/ # Action validation, loop detection, backtracking
38
+ ├── reporting/ # JSON reports, Slack, GitHub annotations
39
+ └── training/ # Experimental MPNet fine-tuning (not wired into runs)
40
+ api/ # SaaS backend (FastAPI + PostgreSQL) — see docs/api.md
41
+ dashboard/ # React dashboard
42
+ website/ # Marketing site (Next.js)
43
+ ```
44
+
45
+ Design decisions are recorded in [docs/adr](docs/adr) — start with
46
+ [ADR-007](docs/adr/ADR-007-llm-capacity-and-execution.md).
47
+
48
+ ---
49
+
50
+ ## 🚀 Quickstart
51
+
52
+ ```bash
53
+ pip install "fusiontest[desktop]"
54
+ playwright install chromium
55
+
56
+ export GROQ_API_KEY=gsk_... # or ANTHROPIC_API_KEY / GEMINI_API_KEY / OPENAI_API_KEY
57
+
58
+ fusiontest run --url https://example.com \
59
+ --goal "Verify the page heading says Example Domain" --headless
60
+ ```
61
+
62
+ Set `FUSIONTEST_API_KEY` (from the dashboard's API Keys page) to upload each
63
+ run to your FusionTest dashboard.
64
+
65
+ Exit codes: `0` all goals passed · `1` a goal failed · `2` some goals couldn't
66
+ be evaluated (LLM capacity or infrastructure) — not a test failure.
67
+
68
+ > **Full guide:** [docs/getting-started.md](docs/getting-started.md) — platforms,
69
+ > goal files, configuration, secrets, replay, dashboard and CI.
70
+
71
+ ---
72
+
73
+ ## 📝 Writing goals
74
+
75
+ ```yaml
76
+ # tests/goals/smoke.yaml
77
+ name: Marketing site smoke test
78
+ platform: playwright
79
+ url: https://example.com
80
+ goals:
81
+ - "Open the Pricing page and verify three plan tiers are shown"
82
+ - "Open the Contact page, submit the form empty, and verify required-field errors appear"
83
+ - name: Log in
84
+ goal: "Log in with {{EMAIL}} and {{PASSWORD}} and verify the dashboard loads"
85
+ ```
86
+
87
+ ```bash
88
+ fusiontest run --file tests/goals/smoke.yaml --headless
89
+ ```
90
+
91
+ Tips: say what to verify, not just where to go; one outcome per goal; don't
92
+ submit real forms on production sites.
93
+
94
+ ---
95
+
96
+ ## 🛠 Configuration
97
+
98
+ `fusiontest init` creates `fusiontest.config.yaml`:
99
+
100
+ ```yaml
101
+ backend: groq # unset = auto-detect from API keys
102
+ guardrails:
103
+ loop_detection_window: 5
104
+ max_retries: 3
105
+ replay:
106
+ enabled: true
107
+ trace_dir: .fusiontest/traces
108
+ reverify_every: 10
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 🔌 CI/CD
114
+
115
+ Run `fusiontest run --headless` in any CI system and cache
116
+ `.fusiontest/traces` so runs can replay. Guides for GitHub Actions, Bitrise,
117
+ Fastlane and Jenkins: [docs/ci-integrations.md](docs/ci-integrations.md).
118
+
119
+ ---
120
+
121
+ ## 🤝 Contributing
122
+
123
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [ROADMAP.md](ROADMAP.md).
124
+
125
+ ## 📄 License
126
+
127
+ MIT © [FusionLeap.io](https://fusionleap.io)
@@ -263,8 +263,9 @@ def run(goal, goal_file, platform, app, url, locale, backend, headless, output,
263
263
  finally:
264
264
  progress.stop()
265
265
 
266
- # Print results table
266
+ # Print results table, then Slack / GitHub annotations if configured
267
267
  _print_results(result)
268
+ _notify(result)
268
269
 
269
270
  # Save JSON report locally
270
271
  if output:
@@ -473,22 +474,19 @@ def init():
473
474
  detected_backend = "groq" # recommended default (free)
474
475
 
475
476
  config = {
476
- # backend: which LLM to use — groq | claude | gemini | gpt4o | ollama | mpnet
477
+ # backend: which LLM to use — groq | claude | gemini | gpt4o | ollama
477
478
  # Free options: groq (recommended), gemini, ollama
478
479
  # Paid options: claude, gpt4o
479
480
  # Leave unset to auto-detect from whichever API key is present.
480
481
  "backend": detected_backend,
481
- "runner": {
482
- "max_steps_per_goal": 25,
483
- },
484
482
  "guardrails": {
485
- "loop_detection": True,
486
483
  "loop_detection_window": 5,
487
484
  "max_retries": 3,
488
- "backtrack_on_invalid": True,
489
485
  },
490
- "reporting": {
491
- "save_recordings": True,
486
+ "replay": {
487
+ "enabled": True,
488
+ "trace_dir": DEFAULT_TRACE_DIR,
489
+ "reverify_every": 10,
492
490
  },
493
491
  }
494
492
 
@@ -594,6 +592,14 @@ def _exit_code(result) -> int:
594
592
  _MODE_LABELS = {"llm": "AI", "replayed": "[cyan]replay[/cyan]", "repaired": "[blue]repaired[/blue]"}
595
593
 
596
594
 
595
+ def _notify(result) -> None:
596
+ """Slack (FUSIONTEST_SLACK_WEBHOOK) and GitHub Actions annotations, when configured."""
597
+ import os
598
+
599
+ from fusiontest.reporting.reporter import Reporter
600
+ Reporter(github_annotations=os.getenv("GITHUB_ACTIONS") == "true").notify(result)
601
+
602
+
597
603
  def _print_results(result) -> None:
598
604
  if result.success:
599
605
  status = "[green]PASS ✓[/green]"
@@ -99,7 +99,6 @@ class RunResult:
99
99
  steps: list[StepResult] = field(default_factory=list)
100
100
  total_duration_seconds: float = 0.0
101
101
  bugs_detected: list[str] = field(default_factory=list)
102
- video_path: str = ""
103
102
  cancelled: bool = False
104
103
  # Set when the run stopped early because it could not be evaluated
105
104
  # (e.g. no LLM backend available). Remaining goals were not run.
@@ -160,9 +159,6 @@ class RunnerConfig:
160
159
  max_goal_seconds: int = 180
161
160
  # Path to write step-level JSONL logs (used by the training pipeline)
162
161
  step_log_path: str | None = None
163
- # Video recording
164
- record_video: bool = False
165
- video_output_dir: str = "fusiontest-reports/videos"
166
162
  # Named secrets for {{VAR}} template resolution in goal strings.
167
163
  # Keys are variable names, values are secret strings.
168
164
  # Env vars are also searched when a key is missing here, unless
@@ -221,14 +217,6 @@ class FusionTestRunner:
221
217
  pathlib.Path(self.config.step_log_path).parent.mkdir(parents=True, exist_ok=True)
222
218
  self._step_log = open(self.config.step_log_path, "a") # noqa: SIM115
223
219
 
224
- self._recorder = None
225
- if self.config.record_video:
226
- from fusiontest.recording.recorder import Recorder, RecordingConfig
227
- self._recorder = Recorder(
228
- platform=getattr(adapter, "_platform", "unknown"),
229
- config=RecordingConfig(output_dir=self.config.video_output_dir),
230
- )
231
-
232
220
  def run(
233
221
  self,
234
222
  goals: list[str],
@@ -255,12 +243,6 @@ class FusionTestRunner:
255
243
  run_start = time.time()
256
244
  result = RunResult(name=name, platform=platform, locale=locale, success=True, url=url)
257
245
 
258
- if self._recorder:
259
- try:
260
- self._recorder.start(getattr(self.adapter, "_driver", self.adapter))
261
- except Exception as e:
262
- logger.warning(f"Could not start recorder: {e}")
263
-
264
246
  logger.info(f"Starting run: {name} ({len(goals)} goals)")
265
247
 
266
248
  # Goals keep their {{VAR}} placeholders everywhere — model prompts, logs,
@@ -390,14 +372,6 @@ class FusionTestRunner:
390
372
  )
391
373
  break
392
374
 
393
- if self._recorder:
394
- try:
395
- video_path = self._recorder.stop()
396
- if video_path:
397
- result.video_path = str(video_path)
398
- except Exception as e:
399
- logger.warning(f"Could not stop recorder: {e}")
400
-
401
375
  result.total_duration_seconds = time.time() - run_start
402
376
  logger.info(
403
377
  f"Run complete: {'PASS' if result.success else 'FAIL'} "
@@ -14,7 +14,6 @@ Supports:
14
14
  from __future__ import annotations
15
15
 
16
16
  import asyncio
17
- import base64
18
17
  import logging
19
18
  import os
20
19
  import re
@@ -636,47 +635,3 @@ class PlaywrightAdapter:
636
635
  ).first
637
636
 
638
637
  raise ValueError(f"Cannot resolve locator for action: {action}")
639
-
640
- # ── Vision fallback ────────────────────────────────────────────────────────
641
-
642
- async def get_vision_description(self) -> str:
643
- """
644
- Fallback for pages with poor accessibility trees.
645
- Takes a screenshot and returns a GPT-4V/Claude description of the UI.
646
- """
647
- import os
648
-
649
- screenshot = await self.async_get_screenshot()
650
- b64 = base64.b64encode(screenshot).decode()
651
-
652
- # Use whichever vision LLM is configured
653
- api_key = os.getenv("OPENAI_API_KEY")
654
- if not api_key:
655
- return "=== SCREEN STATE ===\n-- Vision fallback not configured --"
656
-
657
- try:
658
- from openai import OpenAI
659
- client = OpenAI(api_key=api_key)
660
- resp = client.chat.completions.create(
661
- model="gpt-4o",
662
- messages=[{
663
- "role": "user",
664
- "content": [
665
- {
666
- "type": "text",
667
- "text": (
668
- "Describe all interactive UI elements visible on this screen. "
669
- "For each element, note: type (button, input, link, etc.), "
670
- "visible label or text, and approximate position (top/center/bottom, left/center/right). "
671
- "Format as a list."
672
- ),
673
- },
674
- {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
675
- ],
676
- }],
677
- max_tokens=512,
678
- )
679
- return f"=== SCREEN STATE (vision) ===\n{resp.choices[0].message.content}"
680
- except Exception as e:
681
- logger.warning(f"Vision fallback failed: {e}")
682
- return "=== SCREEN STATE ===\n-- Vision fallback error --"
@@ -64,6 +64,13 @@ class Reporter:
64
64
 
65
65
  return report_path
66
66
 
67
+ def notify(self, result: RunResult) -> None:
68
+ """Send notifications only (Slack, GitHub annotations) — no files written."""
69
+ if self.slack_webhook:
70
+ self._notify_slack(result)
71
+ if self.github_annotations:
72
+ self._emit_github_annotations(result)
73
+
67
74
  # ── JSON ───────────────────────────────────────────────────────────────────
68
75
 
69
76
  def _save_json(self, result: RunResult) -> Path:
@@ -1,6 +1,11 @@
1
1
  """
2
2
  fusiontest.training — MPNet fine-tuning pipeline for FusionTest action ranking.
3
3
 
4
+ EXPERIMENTAL — not wired into runs. The action strings it learns from step
5
+ logs don't match the candidates ActionModel(backend="mpnet") ranks at run
6
+ time, so a model trained here won't help yet. Kept for the distillation plan
7
+ in ADR-001 (amended by ADR-007, which makes replay the main cost lever).
8
+
4
9
  Workflow:
5
10
  1. Collect — gather (screen_text, goal, action, outcome) tuples from live runs
6
11
  2. Build — convert raw logs into contrastive training pairs