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.
- selenium_selector_autocorrect/__init__.py +1 -1
- selenium_selector_autocorrect/ai_providers.py +52 -26
- selenium_selector_autocorrect/auto_correct.py +39 -19
- selenium_selector_autocorrect/correction_tracker.py +517 -57
- selenium_selector_autocorrect/wait_hook.py +50 -19
- {selenium_selector_autocorrect-0.1.0.dist-info → selenium_selector_autocorrect-0.1.2.dist-info}/METADATA +48 -104
- selenium_selector_autocorrect-0.1.2.dist-info/RECORD +10 -0
- selenium_selector_autocorrect/py.typed +0 -1
- selenium_selector_autocorrect-0.1.0.dist-info/RECORD +0 -11
- {selenium_selector_autocorrect-0.1.0.dist-info → selenium_selector_autocorrect-0.1.2.dist-info}/WHEEL +0 -0
- {selenium_selector_autocorrect-0.1.0.dist-info → selenium_selector_autocorrect-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {selenium_selector_autocorrect-0.1.0.dist-info → selenium_selector_autocorrect-0.1.2.dist-info}/top_level.txt +0 -0
|
@@ -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.
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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
|