selenium-python-ai-agent 0.2.1__tar.gz → 0.2.3__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 (42) hide show
  1. {selenium_python_ai_agent-0.2.1/selenium_python_ai_agent.egg-info → selenium_python_ai_agent-0.2.3}/PKG-INFO +3 -3
  2. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/README.md +2 -2
  3. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/pyproject.toml +1 -1
  4. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/__init__.py +1 -1
  5. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/agents/coder.py +64 -1
  6. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/agents/healer.py +20 -4
  7. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/cli.py +1 -1
  8. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/core/orchestrator.py +4 -2
  9. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/scanner/project_scanner.py +32 -10
  10. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3/selenium_python_ai_agent.egg-info}/PKG-INFO +3 -3
  11. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/tests/test_agent_enhancements.py +62 -0
  12. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/tests/test_path_handling.py +1 -1
  13. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/LICENSE +0 -0
  14. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/agents/__init__.py +0 -0
  15. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/agents/definitions.py +0 -0
  16. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/agents/planner.py +0 -0
  17. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/bdd/__init__.py +0 -0
  18. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/bdd/gherkin_advisor.py +0 -0
  19. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/bdd/templates.py +0 -0
  20. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/core/__init__.py +0 -0
  21. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/scanner/__init__.py +0 -0
  22. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/selenium/__init__.py +0 -0
  23. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/selenium/base_page.py +0 -0
  24. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/selenium/driver_factory.py +0 -0
  25. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/selenium/error_map.py +0 -0
  26. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/selenium/locator_advisor.py +0 -0
  27. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/selenium/locator_scanner.py +0 -0
  28. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/__init__.py +0 -0
  29. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/code_validator.py +0 -0
  30. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/config_manager.py +0 -0
  31. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/json_utils.py +0 -0
  32. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/llm.py +0 -0
  33. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/logger.py +0 -0
  34. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/paths.py +0 -0
  35. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/spec_writer.py +0 -0
  36. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_agent/utils/url_extractor.py +0 -0
  37. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_python_ai_agent.egg-info/SOURCES.txt +0 -0
  38. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_python_ai_agent.egg-info/dependency_links.txt +0 -0
  39. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_python_ai_agent.egg-info/entry_points.txt +0 -0
  40. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_python_ai_agent.egg-info/requires.txt +0 -0
  41. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/selenium_python_ai_agent.egg-info/top_level.txt +0 -0
  42. {selenium_python_ai_agent-0.2.1 → selenium_python_ai_agent-0.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: selenium-python-ai-agent
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: AI-powered multi-agent framework to plan, generate and heal Selenium Python tests (pytest + pytest-bdd)
5
5
  Author: Ankit Tripathi
6
6
  License: MIT
@@ -35,7 +35,7 @@ Dynamic: license-file
35
35
 
36
36
  [![PyPI version](https://badge.fury.io/py/selenium-python-ai-agent.svg)](https://pypi.org/project/selenium-python-ai-agent/)
37
37
  [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
38
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/AnkitDeepBlue/selenium-python-ai-agent/blob/main/LICENSE)
39
39
 
40
40
  ---
41
41
 
@@ -405,7 +405,7 @@ Subcommands: `selenium-agent config …`, `selenium-agent init-agents …`, `sel
405
405
 
406
406
  ## 🤝 Contributing
407
407
 
408
- PRs welcome! See [CONTRIBUTING.md](CONTRIBUTING.md)
408
+ PRs welcome! See [CONTRIBUTING.md](https://github.com/AnkitDeepBlue/selenium-python-ai-agent/blob/main/CONTRIBUTING.md)
409
409
 
410
410
  ---
411
411
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  [![PyPI version](https://badge.fury.io/py/selenium-python-ai-agent.svg)](https://pypi.org/project/selenium-python-ai-agent/)
7
7
  [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/AnkitDeepBlue/selenium-python-ai-agent/blob/main/LICENSE)
9
9
 
10
10
  ---
11
11
 
@@ -375,7 +375,7 @@ Subcommands: `selenium-agent config …`, `selenium-agent init-agents …`, `sel
375
375
 
376
376
  ## 🤝 Contributing
377
377
 
378
- PRs welcome! See [CONTRIBUTING.md](CONTRIBUTING.md)
378
+ PRs welcome! See [CONTRIBUTING.md](https://github.com/AnkitDeepBlue/selenium-python-ai-agent/blob/main/CONTRIBUTING.md)
379
379
 
380
380
  ---
381
381
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "selenium-python-ai-agent"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "AI-powered multi-agent framework to plan, generate and heal Selenium Python tests (pytest + pytest-bdd)"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -15,6 +15,6 @@ Usage (CLI):
15
15
 
16
16
  from selenium_agent.core.orchestrator import Orchestrator as SeleniumAgent
17
17
 
18
- __version__ = "0.2.1"
18
+ __version__ = "0.2.3"
19
19
  __author__ = "Ankit Tripathi"
20
20
  __all__ = ["SeleniumAgent"]
@@ -245,6 +245,51 @@ Output valid JSON only, no markdown:
245
245
  ]}}
246
246
  """
247
247
 
248
+ PROJECT_NATIVE_SYSTEM_PROMPT = """
249
+ You are an expert Selenium Python engineer extending an EXISTING test
250
+ framework. The project profile below — especially its sample code — is the
251
+ ONLY architecture reference. You are a guest in this codebase: mirror its
252
+ patterns exactly, never import your own.
253
+
254
+ {project_context}
255
+
256
+ ══ HARD RULES ══
257
+
258
+ 1. FILE LOCATIONS — write ONLY into the project's own folders:
259
+ page objects → {pages_dir}/
260
+ test files → {tests_dir}/
261
+ FORBIDDEN: creating new top-level folders (generated_tests/, output/, ...).
262
+
263
+ 2. ARCHITECTURE — copy the sample page object's style exactly: same base
264
+ class (or none), same helper/service/logger imports, same constructor
265
+ signature, same method style. Do NOT import selenium_agent modules
266
+ unless the samples themselves do.
267
+
268
+ 3. DRIVER — the project's conftest.py already provides the fixture named
269
+ '{fixture_name}'. Test functions accept it as a parameter:
270
+ def test_something({fixture_name}):
271
+ NEVER define fixtures, NEVER create drivers, NEVER assume the fixture
272
+ is called 'driver' if the project calls it something else.
273
+
274
+ 4. IMPORTS — follow the project's import style shown in the samples
275
+ (e.g. 'from pages.login_page import LoginPage').
276
+
277
+ ══ QUALITY RULES (always apply) ══
278
+ - Use ONLY locators from the plan / DOM scan — never invent
279
+ - Unique runtime test data for entity-creating flows:
280
+ import uuid; unique = uuid.uuid4().hex[:8]
281
+ email = "qa." + unique + "@example.com"; password = "Xk9#" + unique + "!Qz"
282
+ - Fill EVERY form field the scan shows, not just the ones named
283
+ - Assert POST-action outcomes (what changes/appears), never pre-action text
284
+ - No time.sleep(), no pytest.skip() placeholders, no TODO steps
285
+
286
+ Output valid JSON only, no markdown:
287
+ {{"files": [
288
+ {{"filename": "{pages_dir}/<name>_page.py", "content": "..."}},
289
+ {{"filename": "{tests_dir}/test_<name>.py", "content": "..."}}
290
+ ]}}
291
+ """
292
+
248
293
  REPAIR_PROMPT = """
249
294
  The code you generated has validation errors. Fix them and return the
250
295
  COMPLETE corrected set of files in the same JSON format.
@@ -270,7 +315,7 @@ class CoderAgent:
270
315
  mode = plan.get("mode", "pytest")
271
316
  logger.info(f"💻 Generating [{mode.upper()}] code (headless={plan.get('headless', False)})...")
272
317
 
273
- system_prompt = CODER_SYSTEM_PROMPT_BDD if mode == "bdd" else CODER_SYSTEM_PROMPT_PYTEST
318
+ system_prompt = self._system_prompt_for(mode, project_profile)
274
319
  user_prompt = self._build_user_prompt(plan, project_profile, mode)
275
320
  max_tokens = self._token_budget(plan)
276
321
 
@@ -375,6 +420,24 @@ class CoderAgent:
375
420
 
376
421
  # ── Prompt building ────────────────────────────────────────────────
377
422
 
423
+ @staticmethod
424
+ def _system_prompt_for(mode: str, project_profile=None) -> str:
425
+ """
426
+ Standalone mode teaches the framework's own BasePage architecture.
427
+ Project mode REPLACES that entirely: the user's existing framework
428
+ (its samples, folders, fixture name) is the only reference —
429
+ supplementing a strong default architecture with a small 'fit in'
430
+ note demonstrably loses to the baked-in style.
431
+ """
432
+ if project_profile is not None and mode != "bdd":
433
+ return PROJECT_NATIVE_SYSTEM_PROMPT.format(
434
+ project_context=project_profile.to_llm_context(),
435
+ pages_dir=project_profile.pages_dir.rstrip("/"),
436
+ tests_dir=project_profile.tests_dir.rstrip("/"),
437
+ fixture_name=project_profile.driver_fixture_name,
438
+ )
439
+ return CODER_SYSTEM_PROMPT_BDD if mode == "bdd" else CODER_SYSTEM_PROMPT_PYTEST
440
+
378
441
  def _build_user_prompt(self, plan: dict, project_profile, mode: str) -> str:
379
442
  project_context = ""
380
443
  if project_profile:
@@ -483,7 +483,8 @@ class HealerAgent:
483
483
 
484
484
  # ── Main heal loop ─────────────────────────────────────────────────
485
485
 
486
- def heal(self, saved_files: list[str], test_filter: str | None = None) -> dict:
486
+ def heal(self, saved_files: list[str], test_filter: str | None = None,
487
+ project_profile=None) -> dict:
487
488
  resolved = self._resolve_paths(saved_files)
488
489
 
489
490
  extra = self._auto_discover_related_files(resolved)
@@ -519,7 +520,8 @@ class HealerAgent:
519
520
 
520
521
  logger.warning(f"❌ Failed on attempt {attempt}")
521
522
  try:
522
- applied = self._fix_once(resolved, output, test_filter, scan_cache)
523
+ applied = self._fix_once(resolved, output, test_filter, scan_cache,
524
+ project_profile=project_profile)
523
525
  except LLMJSONError as exc:
524
526
  logger.error(f"💥 Healer LLM returned unusable JSON: {exc}")
525
527
  applied = False
@@ -537,7 +539,8 @@ class HealerAgent:
537
539
  return {"status": "failed", "attempts": self.max_retries, "output": output}
538
540
 
539
541
  def _fix_once(self, resolved: dict[str, Path], output: str,
540
- test_filter: str | None, scan_cache: dict[str, str]) -> bool:
542
+ test_filter: str | None, scan_cache: dict[str, str],
543
+ project_profile=None) -> bool:
541
544
  """One LLM fix round. Returns True if at least one file was updated."""
542
545
  known_fix = SeleniumErrorMap.get_fix_summary(output)
543
546
 
@@ -582,6 +585,18 @@ class HealerAgent:
582
585
  system_prompt = HEALER_SYSTEM_PROMPT
583
586
  target_instruction = ""
584
587
 
588
+ project_instruction = ""
589
+ if project_profile is not None:
590
+ project_instruction = (
591
+ f"\n\n🏗️ EXISTING-PROJECT MODE — this codebase has its OWN "
592
+ f"architecture. The profile below OVERRIDES the default "
593
+ f"architecture/import rules above: mirror the project's "
594
+ f"patterns exactly, never convert files to the "
595
+ f"selenium_agent BasePage style, and tests use the fixture "
596
+ f"named '{project_profile.driver_fixture_name}'.\n\n"
597
+ f"{project_profile.to_llm_context()}\n"
598
+ )
599
+
585
600
  raw = self.client.generate_text(
586
601
  system_prompt=system_prompt,
587
602
  user_prompt=(
@@ -589,7 +604,8 @@ class HealerAgent:
589
604
  f"SELENIUM ERROR ANALYSIS:\n{known_fix}\n\n"
590
605
  f"{locator_context}\n"
591
606
  f"PYTEST OUTPUT:\n{self._trim_output(output)}\n"
592
- f"{target_instruction}\n"
607
+ f"{target_instruction}"
608
+ f"{project_instruction}\n"
593
609
  f"CURRENT CODE:\n{files_text}"
594
610
  ),
595
611
  max_tokens=8000,
@@ -201,7 +201,7 @@ Other examples:
201
201
  parser.add_argument("--test", default=None, metavar="TEST_NAME",
202
202
  help="Specific test function to run/heal. "
203
203
  "e.g. --test test_login_locked_out_user")
204
- parser.add_argument("--version", action="version", version="selenium-agent 0.2.1")
204
+ parser.add_argument("--version", action="version", version="selenium-agent 0.2.3")
205
205
 
206
206
  args = parser.parse_args()
207
207
 
@@ -148,7 +148,8 @@ class Orchestrator:
148
148
  return self.coder.code(plan, project_profile=self.project_profile)
149
149
 
150
150
  def heal_only(self, file_paths: list, test_filter: str | None = None) -> dict:
151
- return self.healer.heal(file_paths, test_filter=test_filter)
151
+ return self.healer.heal(file_paths, test_filter=test_filter,
152
+ project_profile=self.project_profile)
152
153
 
153
154
  def scan_only(self, project_root: str) -> str:
154
155
  from selenium_agent.scanner.project_scanner import ProjectScanner
@@ -183,7 +184,8 @@ class Orchestrator:
183
184
  heal_result = None
184
185
  if self.auto_heal:
185
186
  logger.info("\n🩺 STEP 3: HEALER")
186
- heal_result = self.healer.heal(saved_files)
187
+ heal_result = self.healer.heal(saved_files,
188
+ project_profile=self.project_profile)
187
189
  else:
188
190
  logger.info("\n⏭️ Auto-heal skipped")
189
191
  return saved_files, heal_result
@@ -54,6 +54,7 @@ class ProjectProfile:
54
54
  # Driver setup
55
55
  driver_setup: str = "fixture" # fixture | setUp | class-level
56
56
  driver_scope: str = "function" # function | class | module
57
+ driver_fixture_name: str = "driver" # actual fixture name (e.g. 'browser')
57
58
  browser: str = "chrome"
58
59
  headless: bool = False
59
60
 
@@ -91,7 +92,8 @@ class ProjectProfile:
91
92
  f"BDD detected : {self.has_bdd}",
92
93
  f"Base Page class : {self.base_page_class}",
93
94
  f"Base Page import : {self.base_page_import or 'not detected'}",
94
- f"Driver setup : {self.driver_setup} (scope={self.driver_scope})",
95
+ f"Driver setup : {self.driver_setup} (scope={self.driver_scope}, "
96
+ f"fixture name='{self.driver_fixture_name}')",
95
97
  f"Browser : {self.browser} (headless={self.headless})",
96
98
  f"Test file pattern : {self.test_file_prefix}<name>.py",
97
99
  f"Page file pattern : <name>{self.page_file_suffix}.py",
@@ -169,12 +171,18 @@ class ProjectScanner:
169
171
  # ── Detection methods ─────────────────────────────────────────────
170
172
 
171
173
  def _detect_folders(self):
172
- """Find pages, tests, features, steps directories."""
173
- all_dirs = [
174
- d for d in self.root.rglob("*")
175
- if d.is_dir() and not self._is_ignored(d)
176
- ]
177
- dir_names = {d.name.lower(): d for d in all_dirs}
174
+ """Find pages, tests, features, steps directories.
175
+
176
+ Shallowest directory wins: a project's real `pages/` at the root
177
+ must beat any deeper `something/pages/` twin."""
178
+ all_dirs = sorted(
179
+ (d for d in self.root.rglob("*")
180
+ if d.is_dir() and not self._is_ignored(d)),
181
+ key=lambda d: len(d.parts),
182
+ )
183
+ dir_names: dict = {}
184
+ for d in all_dirs:
185
+ dir_names.setdefault(d.name.lower(), d)
178
186
 
179
187
  for name in self.PAGES_DIRS:
180
188
  if name in dir_names:
@@ -301,11 +309,19 @@ class ProjectScanner:
301
309
  continue
302
310
 
303
311
  # Fixture pattern
304
- if "@pytest.fixture" in content and "driver" in content:
312
+ if "@pytest.fixture" in content and "driver" in content.lower():
305
313
  self.profile.driver_setup = "fixture"
306
314
  scope_match = re.search(r'@pytest\.fixture\s*\(\s*scope=["\'](\w+)["\']', content)
307
315
  if scope_match:
308
316
  self.profile.driver_scope = scope_match.group(1)
317
+ # The fixture NAME matters — generated tests must request the
318
+ # project's actual fixture ('browser', 'web_driver', ...),
319
+ # not assume it is called 'driver'.
320
+ name_match = re.search(
321
+ r"@pytest\.fixture(?:\([^)]*\))?\s*\ndef\s+(\w+)\s*\(", content
322
+ )
323
+ if name_match and f.name == "conftest.py":
324
+ self.profile.driver_fixture_name = name_match.group(1)
309
325
 
310
326
  # unittest setUp pattern
311
327
  elif "def setUp(self)" in content:
@@ -445,8 +461,14 @@ class ProjectScanner:
445
461
  continue
446
462
 
447
463
  def _is_ignored(self, path: Path) -> bool:
448
- """Skip venv, cache, git, node_modules etc."""
464
+ """Skip venv, cache, git, node_modules etc.
465
+
466
+ Also skips the agent's OWN output folders (generated_tests/, specs/):
467
+ scanning them would make the profile describe the agent's previous
468
+ output instead of the user's real framework — circular pollution.
469
+ """
449
470
  ignored = {".venv", "venv", "__pycache__", ".git", ".idea",
450
471
  "node_modules", ".pytest_cache", "dist", "build",
451
- ".eggs", "*.egg-info", ".tox", ".mypy_cache"}
472
+ ".eggs", "*.egg-info", ".tox", ".mypy_cache",
473
+ "generated_tests", "specs"}
452
474
  return any(part in ignored for part in path.parts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: selenium-python-ai-agent
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: AI-powered multi-agent framework to plan, generate and heal Selenium Python tests (pytest + pytest-bdd)
5
5
  Author: Ankit Tripathi
6
6
  License: MIT
@@ -35,7 +35,7 @@ Dynamic: license-file
35
35
 
36
36
  [![PyPI version](https://badge.fury.io/py/selenium-python-ai-agent.svg)](https://pypi.org/project/selenium-python-ai-agent/)
37
37
  [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://python.org)
38
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/AnkitDeepBlue/selenium-python-ai-agent/blob/main/LICENSE)
39
39
 
40
40
  ---
41
41
 
@@ -405,7 +405,7 @@ Subcommands: `selenium-agent config …`, `selenium-agent init-agents …`, `sel
405
405
 
406
406
  ## 🤝 Contributing
407
407
 
408
- PRs welcome! See [CONTRIBUTING.md](CONTRIBUTING.md)
408
+ PRs welcome! See [CONTRIBUTING.md](https://github.com/AnkitDeepBlue/selenium-python-ai-agent/blob/main/CONTRIBUTING.md)
409
409
 
410
410
  ---
411
411
 
@@ -358,3 +358,65 @@ def test_config_persists_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch
358
358
  # Clearing: empty string is treated as unset by consumers
359
359
  config_manager.save({"project": ""})
360
360
  assert (config_manager.load()["project"] or None) is None
361
+
362
+
363
+ # ── project-native mode ────────────────────────────────────────────────
364
+
365
+
366
+ def _make_fake_project(tmp_path: Path):
367
+ (tmp_path / "pages").mkdir()
368
+ (tmp_path / "tests").mkdir()
369
+ (tmp_path / "generated_tests" / "pages").mkdir(parents=True)
370
+ (tmp_path / "pages" / "home_page.py").write_text(
371
+ "from utils.helpers import HelperBot\n\nclass HomePage:\n def __init__(self, driver):\n self.bot = HelperBot(driver)\n"
372
+ )
373
+ (tmp_path / "generated_tests" / "pages" / "old_page.py").write_text(
374
+ "from selenium_agent.selenium.base_page import BasePage\nclass OldPage(BasePage):\n pass\n"
375
+ )
376
+ (tmp_path / "tests" / "conftest.py").write_text(
377
+ "import pytest\nfrom selenium import webdriver\n\n"
378
+ "@pytest.fixture(scope=\"session\")\ndef browser():\n"
379
+ " d = webdriver.Chrome()\n yield d\n d.quit()\n"
380
+ )
381
+ (tmp_path / "tests" / "test_home.py").write_text(
382
+ "def test_home(browser):\n assert browser\n"
383
+ )
384
+
385
+
386
+ def test_scanner_ignores_own_output_and_prefers_shallow_dirs(tmp_path: Path):
387
+ from selenium_agent.scanner.project_scanner import ProjectScanner
388
+
389
+ _make_fake_project(tmp_path)
390
+ profile = ProjectScanner(str(tmp_path)).scan()
391
+
392
+ assert profile.pages_dir == "pages" # not generated_tests/pages
393
+ assert "selenium_agent" not in profile.sample_page_code # own output excluded
394
+ assert "HelperBot" in profile.sample_page_code # the REAL project style
395
+
396
+
397
+ def test_scanner_detects_fixture_name(tmp_path: Path):
398
+ from selenium_agent.scanner.project_scanner import ProjectScanner
399
+
400
+ _make_fake_project(tmp_path)
401
+ profile = ProjectScanner(str(tmp_path)).scan()
402
+
403
+ assert profile.driver_fixture_name == "browser"
404
+ assert "fixture name='browser'" in profile.to_llm_context()
405
+
406
+
407
+ def test_project_native_prompt_replaces_default_architecture(tmp_path: Path):
408
+ from selenium_agent.agents.coder import CoderAgent
409
+ from selenium_agent.scanner.project_scanner import ProjectScanner
410
+
411
+ _make_fake_project(tmp_path)
412
+ profile = ProjectScanner(str(tmp_path)).scan()
413
+ prompt = CoderAgent._system_prompt_for("pytest", profile)
414
+
415
+ assert "def test_something(browser):" in prompt
416
+ assert "generated_tests/" in prompt # named as FORBIDDEN
417
+ assert "HelperBot" in prompt # project's sample code included
418
+ assert "selenium_agent.selenium.base_page" not in prompt # our BasePage gone
419
+
420
+ # Standalone mode is unchanged
421
+ standalone = CoderAgent._system_prompt_for("pytest", None)
422
+ assert "selenium_agent.selenium.base_page" in standalone
@@ -25,7 +25,7 @@ def make_healer(output_dir: Path) -> HealerAgent:
25
25
 
26
26
 
27
27
  def test_package_import_exposes_public_api():
28
- assert selenium_agent.__version__ == "0.2.1"
28
+ assert selenium_agent.__version__ == "0.2.3"
29
29
  assert selenium_agent.SeleniumAgent.__name__ == "Orchestrator"
30
30
 
31
31