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.
@@ -2,9 +2,10 @@
2
2
 
3
3
  import logging
4
4
  import time
5
- from typing import Callable, Optional
5
+ from typing import Any, Callable, Optional, Tuple, TypeVar, Union, cast
6
6
 
7
7
  from selenium.common.exceptions import TimeoutException
8
+ from selenium.webdriver.remote.webdriver import WebDriver
8
9
  from selenium.webdriver.remote.webelement import WebElement
9
10
  from selenium.webdriver.support.wait import WebDriverWait
10
11
 
@@ -13,19 +14,41 @@ from .correction_tracker import record_correction
13
14
 
14
15
  logger = logging.getLogger(__name__)
15
16
 
16
- _original_until = WebDriverWait.until
17
+ T = TypeVar("T")
18
+ DriverType = Union[WebDriver, WebElement]
17
19
 
20
+ _original_until: Callable[..., Any] = WebDriverWait.until
18
21
 
19
- def _patched_until(self, method: Callable, message: str = ""):
22
+
23
+ def _patched_until(self: WebDriverWait, method: Callable[[WebDriver], T], message: str = "") -> T:
20
24
  """Patched until method with auto-correct support."""
21
- screen = None
22
- stacktrace = None
25
+ screen: Optional[str] = None
26
+ stacktrace: Optional[str] = None
23
27
 
24
28
  end_time = time.monotonic() + self._timeout
25
29
  while True:
26
30
  try:
27
31
  value = method(self._driver)
28
32
  if value:
33
+ # Check if we should suggest a better selector for the found element
34
+ auto_correct = get_auto_correct()
35
+ if auto_correct.suggest_better_selectors and isinstance(value, WebElement):
36
+ locator = _extract_locator_from_method(method)
37
+ if locator:
38
+ by, value_str = locator
39
+ suggest_driver: WebDriver
40
+ if isinstance(self._driver, WebElement):
41
+ suggest_driver = self._driver.parent # type: ignore[attr-defined]
42
+ else:
43
+ suggest_driver = self._driver
44
+
45
+ better_suggestion = auto_correct.suggest_better_selector(
46
+ suggest_driver, by, value_str, value
47
+ )
48
+ if better_suggestion:
49
+ better_by, better_value = better_suggestion
50
+ logger.info(f"[AUTO-SUGGEST] Found element with {by}='{value_str[:50]}...'")
51
+ logger.info(f"[AUTO-SUGGEST] Suggested better selector: {better_by}='{better_value}'")
29
52
  return value
30
53
  except self._ignored_exceptions as exc:
31
54
  screen = getattr(exc, "screen", None)
@@ -43,9 +66,11 @@ def _patched_until(self, method: Callable, message: str = ""):
43
66
  f"[AUTO-CORRECT] Timeout waiting for element {by}='{value_str[:80]}...' - attempting auto-correction"
44
67
  )
45
68
 
46
- driver = self._driver
47
- if isinstance(driver, WebElement):
48
- driver = driver.parent
69
+ driver: WebDriver
70
+ if isinstance(self._driver, WebElement):
71
+ driver = self._driver.parent # type: ignore[attr-defined]
72
+ else:
73
+ driver = self._driver
49
74
 
50
75
  suggestion = auto_correct.suggest_selector(
51
76
  driver,
@@ -71,35 +96,41 @@ def _patched_until(self, method: Callable, message: str = ""):
71
96
  corrected_value=suggested_value,
72
97
  success=True,
73
98
  )
74
- return result
99
+ return cast(T, result)
75
100
  except Exception as e:
76
101
  logger.warning(f"[AUTO-CORRECT] Suggested selector also failed: {e}")
77
102
 
78
103
  raise TimeoutException(message, screen, stacktrace)
79
104
 
80
105
 
81
- def _extract_locator_from_method(method: Callable) -> Optional[tuple]:
106
+ def _extract_locator_from_method(method: Callable[..., Any]) -> Optional[Tuple[str, str]]:
82
107
  """Extract locator tuple (by, value) from an expected_conditions method."""
83
108
  try:
84
109
  if hasattr(method, "locator"):
85
- logger.debug(f"[AUTO-CORRECT] Found locator attribute: {method.locator}")
86
- return method.locator
110
+ locator: Tuple[str, str] = method.locator
111
+ logger.debug(f"[AUTO-CORRECT] Found locator attribute: {locator}")
112
+ return locator
87
113
 
88
114
  if hasattr(method, "__closure__") and method.__closure__:
89
115
  for cell in method.__closure__:
90
116
  cell_contents = cell.cell_contents
91
117
  logger.debug(f"[AUTO-CORRECT] Checking closure cell: {type(cell_contents)} = {cell_contents}")
92
118
  if isinstance(cell_contents, tuple) and len(cell_contents) == 2:
93
- if isinstance(cell_contents[0], str) and isinstance(cell_contents[1], str):
119
+ first, second = cell_contents
120
+ if isinstance(first, str) and isinstance(second, str):
94
121
  logger.debug(f"[AUTO-CORRECT] Extracted locator from closure: {cell_contents}")
95
- return cell_contents
122
+ return (first, second)
96
123
  logger.warning(f"[AUTO-CORRECT] Could not extract locator from method: {method}")
97
124
  except Exception as e:
98
125
  logger.exception(f"[AUTO-CORRECT] Error extracting locator: {e}")
99
126
  return None
100
127
 
101
128
 
102
- def _create_corrected_method(original_method: Callable, new_by: str, new_value: str) -> Optional[Callable]:
129
+ def _create_corrected_method(
130
+ original_method: Callable[..., Any],
131
+ new_by: str,
132
+ new_value: str
133
+ ) -> Optional[Callable[[WebDriver], Any]]:
103
134
  """Create a new expected condition method with corrected locator."""
104
135
  try:
105
136
  from selenium.webdriver.support import expected_conditions as EC
@@ -128,13 +159,13 @@ def _create_corrected_method(original_method: Callable, new_by: str, new_value:
128
159
  return None
129
160
 
130
161
 
131
- def install_auto_correct_hook():
162
+ def install_auto_correct_hook() -> None:
132
163
  """Install the auto-correct hook into WebDriverWait."""
133
- WebDriverWait.until = _patched_until
164
+ WebDriverWait.until = _patched_until # type: ignore[method-assign,assignment]
134
165
  logger.info("[AUTO-CORRECT] Hook installed into WebDriverWait")
135
166
 
136
167
 
137
- def uninstall_auto_correct_hook():
168
+ def uninstall_auto_correct_hook() -> None:
138
169
  """Remove the auto-correct hook from WebDriverWait."""
139
- WebDriverWait.until = _original_until
170
+ WebDriverWait.until = _original_until # type: ignore[method-assign]
140
171
  logger.info("[AUTO-CORRECT] Hook removed from WebDriverWait")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: selenium-selector-autocorrect
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Automatic Selenium selector correction using AI when elements fail to be found
5
5
  Author-email: Marty Zhou <marty.zhou@example.com>
6
6
  Maintainer-email: Marty Zhou <marty.zhou@example.com>
@@ -75,22 +75,6 @@ element = WebDriverWait(driver, 10).until(
75
75
  )
76
76
  ```
77
77
 
78
- ## AI Service Setup
79
-
80
- This package requires a local AI service with an OpenAI-compatible API. We recommend using **[VS Code Copilot as Service](https://marketplace.visualstudio.com/items?itemName=MartyZhou.vscode-copilot-as-service)**, which exposes GitHub Copilot through a local HTTP server.
81
-
82
- ### Installing VS Code Copilot as Service
83
-
84
- 1. Install from VS Code Marketplace or run:
85
- ```bash
86
- code --install-extension MartyZhou.vscode-copilot-as-service
87
- ```
88
-
89
- 2. The extension automatically starts a server on `http://localhost:8765`
90
-
91
- 3. Requires an active GitHub Copilot subscription
92
-
93
-
94
78
  ## Configuration
95
79
 
96
80
  Configure via environment variables:
@@ -124,67 +108,28 @@ install_auto_correct_hook()
124
108
 
125
109
  ```python
126
110
  from selenium_selector_autocorrect import (
127
- install_auto_correct_hook,
128
111
  get_auto_correct,
129
112
  get_correction_tracker,
130
- export_corrections_report
113
+ export_corrections_report,
131
114
  )
132
115
 
133
- install_auto_correct_hook()
134
-
135
116
  auto_correct = get_auto_correct()
136
117
  auto_correct.enabled = True
137
- auto_correct.suggest_better_selectors = False
138
118
 
139
- # Export corrections report at end of test run
140
119
  tracker = get_correction_tracker()
141
120
  export_corrections_report("corrections_report.json")
142
- tracker = get_correction_tracker()
143
- export_corrections_report("corrections_report.json")
144
-
145
- print(f"Total corrections: {len(tracker.get_corrections())}")
146
- print(f"Successful corrections: {len(tracker.get_successful_corrections())}")
147
121
  ```
148
122
 
149
- ### Custom AI Provider
150
-
151
- ```python
152
- from selenium_selector_autocorrect import AIProvider, configure_provider
153
-
154
- class CustomAIProvider(AIProvider):
155
- def is_available(self) -> bool:
156
- return True
157
-
158
- def suggest_selector(self, system_prompt: str, user_prompt: str):))
159
- ```
160
-
161
- ## How It Works
162
-
163
- 1. **Hook Installation**: Patches `WebDriverWait.until()` to add auto-correction
164
- 2. **Timeout Detection**: When a selector times out, the original exception is caught
165
- 3. **Page Analysis**: JavaScript extracts visible elements and their attributes
166
- 4. **AI Suggestion**: Sends page context to AI provider for selector suggestion
167
- 5. **Verification**: Tests the suggested selector
168
- 6. **Success Handling**: If successful, records the correction and optionally updates the test file
169
- 7. **Fallback**: If correction fails, raises the original TimeoutException
170
-
171
- ## AI Provider Setup
172
-
173
- ### Local AI Service
174
-
175
- The package requires a local AI service with OpenAI-compatible API:
123
+ ## AI Service Setup
176
124
 
177
- ```bash
178
- POST http://localhost:8765/v1/chat/completions
179
- ```
125
+ This package requires a local AI service with an OpenAI-compatible API. The following endpoints are used:
180
126
 
181
- For file auto-updates:
182
- ```bash
183
- POST http://localhost:8765/v1/workspace/files/read
184
- POST http://localhost:8765/v1/workspace/files/edit
185
- ## Correction Reports
127
+ - `POST {LOCAL_AI_API_URL}/v1/chat/completions` — chat completions for suggestions
128
+ - `POST {LOCAL_AI_API_URL}/v1/workspace/files/read` — read file content
129
+ - `POST {LOCAL_AI_API_URL}/v1/workspace/files/edit` — apply edits to files
130
+ - `POST {LOCAL_AI_API_URL}/v1/workspace/files/search` — search workspace
186
131
 
187
- Export correction reports in JSON format:
132
+ ## Exporting Reports
188
133
 
189
134
  ```python
190
135
  from selenium_selector_autocorrect import export_corrections_report
@@ -193,6 +138,7 @@ export_corrections_report("corrections_report.json")
193
138
  ```
194
139
 
195
140
  Report format:
141
+
196
142
  ```json
197
143
  {
198
144
  "corrections": [
@@ -215,13 +161,13 @@ Report format:
215
161
  }
216
162
  ```
217
163
 
218
- ## Best Practices
164
+ ## Troubleshooting
165
+
166
+ **AI service not available**: Ensure the local AI service is running and reachable via `LOCAL_AI_API_URL`.
219
167
 
220
- 1. **Install Once**: Call `install_auto_correct_hook()` once at test suite startup (e.g., in `conftest.py`)
221
- 2. **Review Corrections**: Regularly review correction reports to identify brittle selectors
222
- 3. **Update Tests**: Use auto-update sparingly and review changes before committing
223
- 4. **Monitor AI Service**: Ensure your AI service is running and responsive
224
- 5. **Use Strong Selectors**: The tool helps with failures but writing robust selectors is still preferred
168
+ **Auto-update not running**: Verify `SELENIUM_AUTO_UPDATE_TESTS` is set to `"1"`.
169
+
170
+ **Selector strings not found when updating**: Check quote styles in your source files match those used in the correction.
225
171
 
226
172
  ## Requirements
227
173
 
@@ -231,47 +177,45 @@ Report format:
231
177
 
232
178
  ## License
233
179
 
234
- MITInstall hook once at test suite startup (e.g., in conftest.py)
235
- 2. Review correction reports regularly to identify brittle selectors
236
- 3. Use auto-update sparingly and review changes before committing
237
- 4. Ensure your AI service is running and responsive
238
- 5. Write robust selectors - the tool helps with failures but prevention is better
180
+ MIT
239
181
 
240
- When contributing:
241
- 1. Follow PEP 8 style guidelines
242
- 2. Add tests for new features
243
- 3. Update documentation
244
- 4. No emojis in code or documentation
182
+ ## Contributing
245
183
 
246
- ## Troubleshooting
184
+ Please follow PEP 8, add tests for new features, and update documentation when changing behavior.
247
185
 
248
- ### AI Service Not Available
186
+ See [CHANGELOG.md](CHANGELOG.md) for release notes and version history.
249
187
 
250
- Contributions are welcome! Please:
251
- 1. Follow PEP 8 style guidelines
252
- 2. Add tests for new features
253
- 3. Update documentation
254
- 4. Maintain consistency with existing code
188
+ ## Publishing to PyPI
255
189
 
256
- **Possible causes**:
257
- - `SELENIUM_AUTO_UPDATE_TESTS` not set to `"1"`
258
- - Test file path not detected correctly
259
- - Selector string not found in source file (check quotes)
190
+ Create a PyPI API token at https://pypi.org/manage/account/#api-tokens (recommended scope: project or account) and keep the token secret. PyPI no longer accepts username/password uploads — use the token as the password and `__token__` as the username.
260
191
 
261
- ### No Corrections Happening
262
- Solution: Ensure your local AI service is running on the configured port.
192
+ PowerShell (Windows) example:
263
193
 
264
- ### Test File Not Updated
194
+ ```powershell
195
+ $env:TWINE_USERNAME='__token__'
196
+ $env:TWINE_PASSWORD='pypi-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
197
+ .\venv\Scripts\python.exe -m pip install --upgrade build twine
198
+ .\venv\Scripts\python.exe -m build
199
+ .\venv\Scripts\python.exe -m twine upload dist/*
200
+ ```
265
201
 
266
- Possible causes:
267
- - `SELENIUM_AUTO_UPDATE_TESTS` not set to "1"
268
- - Test file path not detected correctly
269
- - Selector string not found in source file
202
+ Unix / macOS example:
270
203
 
271
- ### No Corrections Happening
204
+ ```bash
205
+ export TWINE_USERNAME='__token__'
206
+ export TWINE_PASSWORD='pypi-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
207
+ python3 -m pip install --upgrade build twine
208
+ python3 -m build
209
+ python3 -m twine upload dist/*
210
+ ```
211
+
212
+ To publish to TestPyPI (verify release first):
213
+
214
+ ```bash
215
+ python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
216
+ ```
272
217
 
273
- Check:
274
- 1. Hook is installed - look for log message
275
- 2. AI service is available - check `get_auto_correct().is_service_available()`
276
- 3. Auto-correct is enabled - c
277
- See CHANGELOG.md for version history and changes.
218
+ Notes:
219
+ - Use an API token (not account password). If using a CI system, store the token in secure secrets.
220
+ - You can add a persistent `~/.pypirc` for repeated uploads; see PyPI documentation for details.
221
+ - If upload fails with a 403, verify the token is correct and has the required scope.
@@ -0,0 +1,10 @@
1
+ selenium_selector_autocorrect/__init__.py,sha256=aP02rJbuJxwqMDuc1Y-LoNA3AMYeoCe0_0q_xYNKnTE,1665
2
+ selenium_selector_autocorrect/ai_providers.py,sha256=MIsnwIgTSQQxL4uex4f_4l886EvGLLXvojbheyMgjuI,5966
3
+ selenium_selector_autocorrect/auto_correct.py,sha256=ME2dxkN4liNC797x1nFn-HAKvyXxl7q2ePv5SNon42w,11335
4
+ selenium_selector_autocorrect/correction_tracker.py,sha256=VXmu-9Qgcknl6DOuIuawG_LQJNr3B_N0eHQDghygQEk,32927
5
+ selenium_selector_autocorrect/wait_hook.py,sha256=Vs1FRtKG73i1Cphgf4a3_P5MTdlrWdeOnmzG1ewnlS4,7659
6
+ selenium_selector_autocorrect-0.1.2.dist-info/licenses/LICENSE,sha256=VRPy6YXF2wA_3MeTDnpa_-6Zgjt8c2C0D_iIyhDkduc,1095
7
+ selenium_selector_autocorrect-0.1.2.dist-info/METADATA,sha256=Oh8ha_xAm2MGYGx_lguQ2_citrtVBoaLSM83u236qGk,7318
8
+ selenium_selector_autocorrect-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
9
+ selenium_selector_autocorrect-0.1.2.dist-info/top_level.txt,sha256=nQ78Mk-XHDhYBckP0tMZvoFAZmZGO4Ec4-e1i61Fdz0,30
10
+ selenium_selector_autocorrect-0.1.2.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- # Type hints marker for PEP 561
@@ -1,11 +0,0 @@
1
- selenium_selector_autocorrect/__init__.py,sha256=WjF3o-hlNlt_-tlFxQqIQ4P3OT9ZB0MhBYyJc9Ef8Hg,1665
2
- selenium_selector_autocorrect/ai_providers.py,sha256=c0DHw1Kyfsa6QNF_Yt2zhEGw7-yynZ2_YT7NAXEIn1Y,4095
3
- selenium_selector_autocorrect/auto_correct.py,sha256=73PhryN0HnMTqhQXZGIy3ahC65IfW2i0IJqf-aOSEVM,10822
4
- selenium_selector_autocorrect/correction_tracker.py,sha256=tvw7S9q8y-wuPYJ1W8pBP72r8dI0okbVHSjaYhnKGbA,9282
5
- selenium_selector_autocorrect/py.typed,sha256=FPdf-6Jjb4gsSRLK88ZhHbrQnHD9pcZQTqaxHzO8neM,33
6
- selenium_selector_autocorrect/wait_hook.py,sha256=U4xD3G3rx6sjcN72qTpH3ET-gY5fhS_X6ADI99CP5uc,5857
7
- selenium_selector_autocorrect-0.1.0.dist-info/licenses/LICENSE,sha256=VRPy6YXF2wA_3MeTDnpa_-6Zgjt8c2C0D_iIyhDkduc,1095
8
- selenium_selector_autocorrect-0.1.0.dist-info/METADATA,sha256=mhCjJVP7EoA_n6_VUnHffytUImc-A0kozBntx5TiiHk,9221
9
- selenium_selector_autocorrect-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
- selenium_selector_autocorrect-0.1.0.dist-info/top_level.txt,sha256=nQ78Mk-XHDhYBckP0tMZvoFAZmZGO4Ec4-e1i61Fdz0,30
11
- selenium_selector_autocorrect-0.1.0.dist-info/RECORD,,