selenium-selector-autocorrect 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.
@@ -15,7 +15,7 @@ Environment Variables:
15
15
  SELENIUM_AUTO_UPDATE_TESTS: Auto-update test files with corrections (default: "0")
16
16
  """
17
17
 
18
- __version__ = "1.0.0"
18
+ __version__ = "0.1.2"
19
19
 
20
20
  from .ai_providers import AIProvider, LocalAIProvider, configure_provider, get_provider
21
21
  from .auto_correct import (
@@ -3,7 +3,7 @@
3
3
  import logging
4
4
  import os
5
5
  from abc import ABC, abstractmethod
6
- from typing import Optional
6
+ from typing import Any, Dict, Optional
7
7
 
8
8
  import requests
9
9
 
@@ -40,22 +40,32 @@ class LocalAIProvider(AIProvider):
40
40
  LOCAL_AI_API_URL environment variable (default: http://localhost:8765)
41
41
  """
42
42
 
43
- def __init__(self, base_url: str = None):
44
- self.base_url = base_url or os.environ.get("LOCAL_AI_API_URL", "http://localhost:8765")
45
- self._available = None
43
+ def __init__(self, base_url: Optional[str] = None) -> None:
44
+ self.base_url: str = base_url or os.environ.get("LOCAL_AI_API_URL", "http://localhost:8765")
45
+ self._available: Optional[bool] = None
46
46
 
47
47
  def is_available(self) -> bool:
48
48
  if self._available is not None:
49
49
  return self._available
50
50
  try:
51
- response = requests.post(
52
- f"{self.base_url}/v1/chat/completions",
53
- json={"messages": [{"role": "user", "content": "test"}], "max_tokens": 1},
54
- timeout=5
55
- )
51
+ url = f"{self.base_url}/v1/chat/completions"
52
+ payload = {"messages": [{"role": "user", "content": "test"}], "max_tokens": 1}
53
+
54
+ logger.debug(f"[AI-REQUEST] POST {url}")
55
+ logger.debug(f"[AI-REQUEST] Payload: {payload}")
56
+
57
+ response = requests.post(url, json=payload, timeout=30)
58
+
59
+ logger.debug(f"[AI-RESPONSE] Status: {response.status_code}")
60
+ logger.debug(f"[AI-RESPONSE] Headers: {dict(response.headers)}")
61
+ logger.debug(f"[AI-RESPONSE] Body length: {len(response.text)} chars")
62
+ if response.text:
63
+ logger.debug(f"[AI-RESPONSE] Body preview: {response.text[:500]}")
64
+
56
65
  self._available = response.status_code in (200, 400)
57
66
  except Exception as e:
58
67
  logger.info(f"Local AI service not available at {self.base_url}: {e}")
68
+ logger.debug(f"[AI-ERROR] Exception details: {type(e).__name__}: {str(e)}")
59
69
  self._available = False
60
70
  return self._available
61
71
 
@@ -70,22 +80,37 @@ class LocalAIProvider(AIProvider):
70
80
  AI response text or None if request fails
71
81
  """
72
82
  try:
73
- response = requests.post(
74
- f"{self.base_url}/v1/chat/completions",
75
- json={
76
- "messages": [
77
- {"role": "system", "content": system_prompt},
78
- {"role": "user", "content": user_prompt}
79
- ],
80
- "temperature": 0.3,
81
- "max_tokens": 500
82
- },
83
- timeout=30
84
- )
83
+ url = f"{self.base_url}/v1/chat/completions"
84
+ payload = {
85
+ "messages": [
86
+ {"role": "system", "content": system_prompt},
87
+ {"role": "user", "content": user_prompt}
88
+ ],
89
+ "temperature": 0.3,
90
+ "max_tokens": 500
91
+ }
92
+
93
+ logger.debug(f"[AI-REQUEST] POST {url}")
94
+ logger.debug(f"[AI-REQUEST] System prompt ({len(system_prompt)} chars): {system_prompt[:200]}...")
95
+ logger.debug(f"[AI-REQUEST] User prompt ({len(user_prompt)} chars): {user_prompt[:200]}...")
96
+ logger.debug(f"[AI-REQUEST] Full payload: {payload}")
97
+
98
+ response = requests.post(url, json=payload, timeout=30)
99
+
100
+ logger.debug(f"[AI-RESPONSE] Status: {response.status_code}")
101
+ logger.debug(f"[AI-RESPONSE] Headers: {dict(response.headers)}")
102
+ logger.debug(f"[AI-RESPONSE] Body length: {len(response.text)} chars")
103
+ logger.debug(f"[AI-RESPONSE] Full body: {response.text[:2000]}")
104
+
85
105
  response.raise_for_status()
86
- data = response.json()
87
- return data.get("choices", [{}])[0].get("message", {}).get("content", "")
106
+ data: Dict[str, Any] = response.json()
107
+ content: Optional[str] = data.get("choices", [{}])[0].get("message", {}).get("content", "")
108
+ logger.debug(f"[AI-PARSED] Content length: {len(content) if content else 0} chars")
109
+ logger.debug(f"[AI-PARSED] Content preview: {content[:500] if content else 'None'}")
110
+ return content
88
111
  except requests.exceptions.HTTPError as e:
112
+ logger.error(f"[AI-ERROR] HTTP Error {e.response.status_code}")
113
+ logger.error(f"[AI-ERROR] Response body: {e.response.text[:1000]}")
89
114
  if e.response.status_code == 503:
90
115
  logger.info(f"Local AI service unavailable (503). Disabling auto-correction.")
91
116
  self._available = False
@@ -93,7 +118,8 @@ class LocalAIProvider(AIProvider):
93
118
  logger.warning(f"Local AI HTTP error: {e}")
94
119
  return None
95
120
  except Exception as e:
96
- logger.warning(f"Local AI request failed: {e}")
121
+ logger.error(f"[AI-ERROR] Request failed: {type(e).__name__}: {str(e)}")
122
+ logger.debug(f"[AI-ERROR] Exception details: {e}", exc_info=True)
97
123
  self._available = False
98
124
  return None
99
125
 
@@ -110,7 +136,7 @@ def get_provider() -> AIProvider:
110
136
  return _provider_instance
111
137
 
112
138
 
113
- def configure_provider(provider: AIProvider):
139
+ def configure_provider(provider: AIProvider) -> None:
114
140
  """Set a custom AI provider.
115
141
 
116
142
  Args:
@@ -120,4 +146,4 @@ def configure_provider(provider: AIProvider):
120
146
  _provider_instance = provider
121
147
 
122
148
 
123
- _provider_instance = None
149
+ _provider_instance: Optional[AIProvider] = None
@@ -4,9 +4,13 @@ import json
4
4
  import logging
5
5
  import os
6
6
  import re
7
- from typing import Any, Dict, Optional, Tuple
7
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
8
8
 
9
- from .ai_providers import get_provider
9
+ from .ai_providers import AIProvider, get_provider
10
+
11
+ if TYPE_CHECKING:
12
+ from selenium.webdriver.remote.webdriver import WebDriver
13
+ from selenium.webdriver.remote.webelement import WebElement
10
14
 
11
15
  logger = logging.getLogger(__name__)
12
16
 
@@ -18,22 +22,22 @@ class SelectorAutoCorrect:
18
22
  enabled: Whether auto-correction is enabled
19
23
  """
20
24
 
21
- def __init__(self, enabled: bool = True):
22
- self.enabled = enabled
23
- self._provider = None
25
+ def __init__(self, enabled: bool = True) -> None:
26
+ self.enabled: bool = enabled
27
+ self._provider: Optional[AIProvider] = None
24
28
  self._correction_cache: Dict[str, str] = {}
25
29
  self._suggestion_cache: Dict[str, str] = {}
26
- self.suggest_better_selectors = os.environ.get("SELENIUM_SUGGEST_BETTER", "0").lower() in ("1", "true", "yes")
27
- self._confidence_threshold = 50
28
- self._cache_enabled = True
30
+ self.suggest_better_selectors: bool = os.environ.get("SELENIUM_SUGGEST_BETTER", "0").lower() in ("1", "true", "yes")
31
+ self._confidence_threshold: int = 50
32
+ self._cache_enabled: bool = True
29
33
 
30
34
  @property
31
- def provider(self):
35
+ def provider(self) -> AIProvider:
32
36
  if self._provider is None:
33
37
  self._provider = get_provider()
34
38
  return self._provider
35
39
 
36
- def set_provider(self, provider) -> None:
40
+ def set_provider(self, provider: AIProvider) -> None:
37
41
  self._provider = provider
38
42
 
39
43
  def is_service_available(self) -> bool:
@@ -42,7 +46,7 @@ class SelectorAutoCorrect:
42
46
  provider = self.provider
43
47
  return provider is not None and provider.is_available()
44
48
 
45
- def get_visible_elements_summary(self, driver) -> str:
49
+ def get_visible_elements_summary(self, driver: "WebDriver") -> str:
46
50
  try:
47
51
  script = """
48
52
  function getElementSummary() {
@@ -86,7 +90,13 @@ class SelectorAutoCorrect:
86
90
  logger.warning(f"Failed to get element summary: {e}")
87
91
  return "[]"
88
92
 
89
- def suggest_selector(self, driver, failed_by: str, failed_value: str, error_message: str = "") -> Optional[Tuple[str, str]]:
93
+ def suggest_selector(
94
+ self,
95
+ driver: "WebDriver",
96
+ failed_by: str,
97
+ failed_value: str,
98
+ error_message: str = ""
99
+ ) -> Optional[Tuple[str, str]]:
90
100
  if not self.enabled or not self.is_service_available():
91
101
  return None
92
102
 
@@ -139,7 +149,13 @@ Suggest a working selector. Respond with ONLY a JSON object."""
139
149
  logger.warning(f"[AUTO-CORRECT] Failed to get suggestion: {e}")
140
150
  return None
141
151
 
142
- def suggest_better_selector(self, driver, current_by: str, current_value: str, element) -> Optional[Tuple[str, str]]:
152
+ def suggest_better_selector(
153
+ self,
154
+ driver: "WebDriver",
155
+ current_by: str,
156
+ current_value: str,
157
+ element: "WebElement"
158
+ ) -> Optional[Tuple[str, str]]:
143
159
  if not self.suggest_better_selectors or not self.enabled or not self.is_service_available():
144
160
  return None
145
161
 
@@ -182,12 +198,12 @@ Available Elements:
182
198
  if self._cache_enabled:
183
199
  self._suggestion_cache[cache_key] = "OPTIMAL"
184
200
  except Exception as e:
185
- logger.debug(f"[SUGGEST] Failed to get better selector: {e}")
201
+ logger.debug(f"[AUTO-SUGGEST] Failed to get better selector: {e}")
186
202
  return None
187
203
 
188
- def _get_element_info(self, element) -> Dict[str, Any]:
204
+ def _get_element_info(self, element: "WebElement") -> Dict[str, Any]:
189
205
  try:
190
- info = {
206
+ info: Dict[str, Any] = {
191
207
  "tag": element.tag_name,
192
208
  "id": element.get_attribute("id"),
193
209
  "name": element.get_attribute("name"),
@@ -223,7 +239,7 @@ Available Elements:
223
239
  logger.warning(f"[AUTO-CORRECT] Error parsing suggestion: {e}")
224
240
  return None
225
241
 
226
- def clear_cache(self):
242
+ def clear_cache(self) -> None:
227
243
  self._correction_cache.clear()
228
244
  self._suggestion_cache.clear()
229
245
 
@@ -240,12 +256,16 @@ def get_auto_correct() -> SelectorAutoCorrect:
240
256
  return _auto_correct_instance
241
257
 
242
258
 
243
- def set_auto_correct_enabled(enabled: bool):
259
+ def set_auto_correct_enabled(enabled: bool) -> None:
244
260
  """Enable or disable auto-correction globally."""
245
261
  get_auto_correct().enabled = enabled
246
262
 
247
263
 
248
- def configure_auto_correct(provider=None, enabled: bool = True, suggest_better: bool = False):
264
+ def configure_auto_correct(
265
+ provider: Optional[AIProvider] = None,
266
+ enabled: bool = True,
267
+ suggest_better: bool = False
268
+ ) -> None:
249
269
  """Configure auto-correction settings."""
250
270
  auto_correct = get_auto_correct()
251
271
  auto_correct.enabled = enabled