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
|
@@ -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
|
-
|
|
17
|
+
T = TypeVar("T")
|
|
18
|
+
DriverType = Union[WebDriver, WebElement]
|
|
17
19
|
|
|
20
|
+
_original_until: Callable[..., Any] = WebDriverWait.until
|
|
18
21
|
|
|
19
|
-
|
|
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
|
|
47
|
-
if isinstance(
|
|
48
|
-
driver =
|
|
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[
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
POST
|
|
184
|
-
POST
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
+
Please follow PEP 8, add tests for new features, and update documentation when changing behavior.
|
|
247
185
|
|
|
248
|
-
|
|
186
|
+
See [CHANGELOG.md](CHANGELOG.md) for release notes and version history.
|
|
249
187
|
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262
|
-
Solution: Ensure your local AI service is running on the configured port.
|
|
192
|
+
PowerShell (Windows) example:
|
|
263
193
|
|
|
264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|