waitless 0.3.1__tar.gz → 0.3.2__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.
- {waitless-0.3.1/waitless.egg-info → waitless-0.3.2}/PKG-INFO +14 -14
- {waitless-0.3.1 → waitless-0.3.2}/README.md +13 -13
- {waitless-0.3.1 → waitless-0.3.2}/pyproject.toml +1 -1
- {waitless-0.3.1 → waitless-0.3.2}/waitless/__init__.py +1 -1
- {waitless-0.3.1 → waitless-0.3.2}/waitless/__main__.py +28 -28
- {waitless-0.3.1 → waitless-0.3.2}/waitless/diagnostics.py +30 -30
- {waitless-0.3.1 → waitless-0.3.2}/waitless/signals.py +1 -10
- {waitless-0.3.1 → waitless-0.3.2/waitless.egg-info}/PKG-INFO +14 -14
- {waitless-0.3.1 → waitless-0.3.2}/LICENSE +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/setup.cfg +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless/config.py +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless/engine.py +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless/exceptions.py +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless/instrumentation.py +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless/selenium_integration.py +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless.egg-info/SOURCES.txt +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless.egg-info/dependency_links.txt +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless.egg-info/entry_points.txt +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless.egg-info/requires.txt +0 -0
- {waitless-0.3.1 → waitless-0.3.2}/waitless.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: waitless
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Eliminate explicit waits in UI automation by detecting true UI stability
|
|
5
5
|
Author-email: Dhiraj Das <dhirajdas.666@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -171,18 +171,18 @@ python -m waitless doctor --file diagnostics.json
|
|
|
171
171
|
|
|
172
172
|
Sample output:
|
|
173
173
|
```
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
174
|
+
+--------------------------------------------------------------------+
|
|
175
|
+
| WAITLESS STABILITY REPORT |
|
|
176
|
+
+--------------------------------------------------------------------+
|
|
177
|
+
| BLOCKING FACTORS: |
|
|
178
|
+
| [!] NETWORK: 2 request(s) still pending |
|
|
179
|
+
| -> GET /api/users |
|
|
180
|
+
| [!] ANIMATIONS: 1 active animation(s) |
|
|
181
|
+
+--------------------------------------------------------------------+
|
|
182
|
+
| SUGGESTIONS: |
|
|
183
|
+
| 1. Set network_idle_threshold=2 for background traffic |
|
|
184
|
+
| 2. Use animation_detection=False for infinite spinners |
|
|
185
|
+
+--------------------------------------------------------------------+
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
## Important Notes
|
|
@@ -214,7 +214,7 @@ element = driver.find_element(By.ID, "button")
|
|
|
214
214
|
original = element.unwrap() # Gets the real WebElement
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
-
## v0.3 Limitations
|
|
217
|
+
## v0.3.2 Limitations
|
|
218
218
|
|
|
219
219
|
- **Selenium only** - Playwright support planned for v1
|
|
220
220
|
- **Sync only** - No async/await support yet
|
|
@@ -141,18 +141,18 @@ python -m waitless doctor --file diagnostics.json
|
|
|
141
141
|
|
|
142
142
|
Sample output:
|
|
143
143
|
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
144
|
+
+--------------------------------------------------------------------+
|
|
145
|
+
| WAITLESS STABILITY REPORT |
|
|
146
|
+
+--------------------------------------------------------------------+
|
|
147
|
+
| BLOCKING FACTORS: |
|
|
148
|
+
| [!] NETWORK: 2 request(s) still pending |
|
|
149
|
+
| -> GET /api/users |
|
|
150
|
+
| [!] ANIMATIONS: 1 active animation(s) |
|
|
151
|
+
+--------------------------------------------------------------------+
|
|
152
|
+
| SUGGESTIONS: |
|
|
153
|
+
| 1. Set network_idle_threshold=2 for background traffic |
|
|
154
|
+
| 2. Use animation_detection=False for infinite spinners |
|
|
155
|
+
+--------------------------------------------------------------------+
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
## Important Notes
|
|
@@ -184,7 +184,7 @@ element = driver.find_element(By.ID, "button")
|
|
|
184
184
|
original = element.unwrap() # Gets the real WebElement
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
-
## v0.3 Limitations
|
|
187
|
+
## v0.3.2 Limitations
|
|
188
188
|
|
|
189
189
|
- **Selenium only** - Playwright support planned for v1
|
|
190
190
|
- **Sync only** - No async/await support yet
|
|
@@ -71,34 +71,34 @@ def run_doctor(args):
|
|
|
71
71
|
return 1
|
|
72
72
|
else:
|
|
73
73
|
# Show usage instructions
|
|
74
|
-
print("
|
|
75
|
-
print("
|
|
76
|
-
print("
|
|
77
|
-
print("
|
|
78
|
-
print("
|
|
79
|
-
print("
|
|
80
|
-
print("
|
|
81
|
-
print("
|
|
82
|
-
print("
|
|
83
|
-
print("
|
|
84
|
-
print("
|
|
85
|
-
print("
|
|
86
|
-
print("
|
|
87
|
-
print("
|
|
88
|
-
print("
|
|
89
|
-
print("
|
|
90
|
-
print("
|
|
91
|
-
print("
|
|
92
|
-
print("
|
|
93
|
-
print("
|
|
94
|
-
print("
|
|
95
|
-
print("
|
|
96
|
-
print("
|
|
97
|
-
print("
|
|
98
|
-
print("
|
|
99
|
-
print("
|
|
100
|
-
print("
|
|
101
|
-
print("
|
|
74
|
+
print("+-" + "-" * 66 + "-+")
|
|
75
|
+
print("|" + "WAITLESS DOCTOR".center(66) + "|")
|
|
76
|
+
print("+-" + "-" * 66 + "-+")
|
|
77
|
+
print("|".ljust(67) + "|")
|
|
78
|
+
print("| The doctor command analyzes stability diagnostics.".ljust(67) + "|")
|
|
79
|
+
print("|".ljust(67) + "|")
|
|
80
|
+
print("| USAGE OPTIONS:".ljust(67) + "|")
|
|
81
|
+
print("|".ljust(67) + "|")
|
|
82
|
+
print("| 1. From a diagnostic file:".ljust(67) + "|")
|
|
83
|
+
print("| waitless doctor --file diagnostics.json".ljust(67) + "|")
|
|
84
|
+
print("|".ljust(67) + "|")
|
|
85
|
+
print("| 2. In your test code, capture diagnostics on failure:".ljust(67) + "|")
|
|
86
|
+
print("|".ljust(67) + "|")
|
|
87
|
+
print("| from waitless import get_diagnostics".ljust(67) + "|")
|
|
88
|
+
print("| from waitless.diagnostics import print_report".ljust(67) + "|")
|
|
89
|
+
print("|".ljust(67) + "|")
|
|
90
|
+
print("| try:".ljust(67) + "|")
|
|
91
|
+
print("| driver.find_element(...).click()".ljust(67) + "|")
|
|
92
|
+
print("| except StabilizationTimeout as e:".ljust(67) + "|")
|
|
93
|
+
print("| print_report(engine) # Print diagnostic report".ljust(67) + "|")
|
|
94
|
+
print("|".ljust(67) + "|")
|
|
95
|
+
print("| 3. Export diagnostics for CI:".ljust(67) + "|")
|
|
96
|
+
print("|".ljust(67) + "|")
|
|
97
|
+
print("| diagnostics = get_diagnostics(driver)".ljust(67) + "|")
|
|
98
|
+
print("| with open('diag.json', 'w') as f:".ljust(67) + "|")
|
|
99
|
+
print("| json.dump(diagnostics, f)".ljust(67) + "|")
|
|
100
|
+
print("|".ljust(67) + "|")
|
|
101
|
+
print("+-" + "-" * 66 + "-+")
|
|
102
102
|
return 0
|
|
103
103
|
|
|
104
104
|
# Generate report
|
|
@@ -24,71 +24,71 @@ class DiagnosticReport:
|
|
|
24
24
|
def generate_text_report(self) -> str:
|
|
25
25
|
"""Generate a text-based diagnostic report."""
|
|
26
26
|
lines = []
|
|
27
|
-
lines.append("
|
|
28
|
-
lines.append("
|
|
29
|
-
lines.append("
|
|
30
|
-
lines.append(f"
|
|
27
|
+
lines.append("+-" + "-" * 66 + "-+")
|
|
28
|
+
lines.append("|" + "WAITLESS STABILITY REPORT".center(66) + "|")
|
|
29
|
+
lines.append("+-" + "-" * 66 + "-+")
|
|
30
|
+
lines.append(f"| Report generated at: {self.timestamp.strftime('%Y-%m-%d %H:%M:%S'):<43} |")
|
|
31
31
|
|
|
32
32
|
config = self.diagnostics.get('config', {})
|
|
33
|
-
lines.append("
|
|
34
|
-
lines.append("
|
|
35
|
-
lines.append(f"
|
|
36
|
-
lines.append(f"
|
|
37
|
-
lines.append(f"
|
|
38
|
-
lines.append(f"
|
|
33
|
+
lines.append("+-" + "-" * 66 + "-+")
|
|
34
|
+
lines.append("| CONFIGURATION:".ljust(67) + "|")
|
|
35
|
+
lines.append(f"| Timeout: {config.get('timeout', 'N/A')}s".ljust(67) + "|")
|
|
36
|
+
lines.append(f"| Strictness: {config.get('strictness', 'N/A')}".ljust(67) + "|")
|
|
37
|
+
lines.append(f"| Network threshold: {config.get('network_idle_threshold', 'N/A')} pending requests".ljust(67) + "|")
|
|
38
|
+
lines.append(f"| Animation detection: {config.get('animation_detection', 'N/A')}".ljust(67) + "|")
|
|
39
39
|
|
|
40
40
|
blocking = self.diagnostics.get('blocking_factors', {})
|
|
41
41
|
if blocking:
|
|
42
|
-
lines.append("
|
|
43
|
-
lines.append("
|
|
44
|
-
lines.append("
|
|
42
|
+
lines.append("+-" + "-" * 66 + "-+")
|
|
43
|
+
lines.append("| BLOCKING FACTORS:".ljust(67) + "|")
|
|
44
|
+
lines.append("|".ljust(67) + "|")
|
|
45
45
|
|
|
46
46
|
pending = blocking.get('pending_requests', 0)
|
|
47
47
|
if pending > 0:
|
|
48
|
-
lines.append("
|
|
48
|
+
lines.append("| [!] NETWORK: {} request(s) still pending".format(pending).ljust(67) + "|")
|
|
49
49
|
|
|
50
50
|
details = blocking.get('pending_request_details', [])
|
|
51
51
|
for req in details[:5]: # Show max 5
|
|
52
52
|
url = req.get('url', 'unknown')[:50]
|
|
53
53
|
started = req.get('startTime', 0)
|
|
54
|
-
lines.append(f"
|
|
54
|
+
lines.append(f"| -> {req.get('type', 'unknown').upper()} {url}".ljust(67) + "|")
|
|
55
55
|
|
|
56
56
|
if len(details) > 5:
|
|
57
|
-
lines.append(f"
|
|
58
|
-
lines.append("
|
|
57
|
+
lines.append(f"| ... and {len(details) - 5} more".ljust(67) + "|")
|
|
58
|
+
lines.append("|".ljust(67) + "|")
|
|
59
59
|
|
|
60
60
|
animations = blocking.get('active_animations', 0)
|
|
61
61
|
if animations > 0:
|
|
62
|
-
lines.append(f"
|
|
63
|
-
lines.append("
|
|
62
|
+
lines.append(f"| [!] ANIMATIONS: {animations} active animation(s)".ljust(67) + "|")
|
|
63
|
+
lines.append("|".ljust(67) + "|")
|
|
64
64
|
|
|
65
65
|
if blocking.get('layout_shifting'):
|
|
66
|
-
lines.append("
|
|
67
|
-
lines.append("
|
|
66
|
+
lines.append("| [!] LAYOUT: Elements are still moving".ljust(67) + "|")
|
|
67
|
+
lines.append("|".ljust(67) + "|")
|
|
68
68
|
|
|
69
69
|
status = self.diagnostics.get('last_status')
|
|
70
70
|
if status:
|
|
71
|
-
lines.append("
|
|
72
|
-
lines.append("
|
|
71
|
+
lines.append("+-" + "-" * 66 + "-+")
|
|
72
|
+
lines.append("| SIGNAL STATUS:".ljust(67) + "|")
|
|
73
73
|
|
|
74
74
|
for signal in status.get('signals', []):
|
|
75
|
-
state = "
|
|
75
|
+
state = "[OK]" if signal['state'] == 'STABLE' else "[WAIT]"
|
|
76
76
|
mandatory = "[M]" if signal['mandatory'] else "[O]"
|
|
77
|
-
line = f"
|
|
78
|
-
lines.append(line[:66].ljust(67) + "
|
|
77
|
+
line = f"| {state} {mandatory} {signal['type']}: {signal.get('details', 'N/A')}"
|
|
78
|
+
lines.append(line[:66].ljust(67) + "|")
|
|
79
79
|
|
|
80
80
|
timeline = self.diagnostics.get('timeline', [])
|
|
81
81
|
if timeline:
|
|
82
|
-
lines.append("
|
|
83
|
-
lines.append("
|
|
82
|
+
lines.append("+-" + "-" * 66 + "-+")
|
|
83
|
+
lines.append("| RECENT EVENTS (last 10):".ljust(67) + "|")
|
|
84
84
|
|
|
85
85
|
for entry in timeline[-10:]:
|
|
86
86
|
time_str = str(entry.get('time', ''))[-6:]
|
|
87
87
|
msg = entry.get('message', '')[:50]
|
|
88
|
-
lines.append(f"
|
|
88
|
+
lines.append(f"| [{time_str}] {msg}".ljust(67) + "|")
|
|
89
89
|
|
|
90
90
|
lines.extend(self._generate_suggestions())
|
|
91
|
-
lines.append("
|
|
91
|
+
lines.append("+-" + "-" * 66 + "-+")
|
|
92
92
|
|
|
93
93
|
return "\n".join(lines)
|
|
94
94
|
|
|
@@ -169,18 +169,9 @@ class SignalEvaluator:
|
|
|
169
169
|
# Primary: Use mutation rate if available
|
|
170
170
|
if mutation_rate is not None:
|
|
171
171
|
threshold = self.config.mutation_rate_threshold
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
# Also check settle time to ensure we are quiet AFTER a mutation
|
|
175
|
-
time_since_mutation = (current_time * 1000) - last_mutation
|
|
176
|
-
threshold_ms = self.config.dom_settle_time * 1000
|
|
177
|
-
settle_stable = time_since_mutation >= threshold_ms
|
|
178
|
-
|
|
179
|
-
is_stable = rate_stable and settle_stable
|
|
172
|
+
is_stable = mutation_rate <= threshold
|
|
180
173
|
|
|
181
174
|
details = f"Mutation rate: {mutation_rate:.0f}/sec (threshold: {threshold:.0f}/sec)"
|
|
182
|
-
if not settle_stable:
|
|
183
|
-
details += f", but only {time_since_mutation:.0f}ms since last mutation (need {threshold_ms:.0f}ms)"
|
|
184
175
|
|
|
185
176
|
return Signal(
|
|
186
177
|
signal_type=SignalType.DOM_MUTATIONS,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: waitless
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Eliminate explicit waits in UI automation by detecting true UI stability
|
|
5
5
|
Author-email: Dhiraj Das <dhirajdas.666@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -171,18 +171,18 @@ python -m waitless doctor --file diagnostics.json
|
|
|
171
171
|
|
|
172
172
|
Sample output:
|
|
173
173
|
```
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
174
|
+
+--------------------------------------------------------------------+
|
|
175
|
+
| WAITLESS STABILITY REPORT |
|
|
176
|
+
+--------------------------------------------------------------------+
|
|
177
|
+
| BLOCKING FACTORS: |
|
|
178
|
+
| [!] NETWORK: 2 request(s) still pending |
|
|
179
|
+
| -> GET /api/users |
|
|
180
|
+
| [!] ANIMATIONS: 1 active animation(s) |
|
|
181
|
+
+--------------------------------------------------------------------+
|
|
182
|
+
| SUGGESTIONS: |
|
|
183
|
+
| 1. Set network_idle_threshold=2 for background traffic |
|
|
184
|
+
| 2. Use animation_detection=False for infinite spinners |
|
|
185
|
+
+--------------------------------------------------------------------+
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
## Important Notes
|
|
@@ -214,7 +214,7 @@ element = driver.find_element(By.ID, "button")
|
|
|
214
214
|
original = element.unwrap() # Gets the real WebElement
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
-
## v0.3 Limitations
|
|
217
|
+
## v0.3.2 Limitations
|
|
218
218
|
|
|
219
219
|
- **Selenium only** - Playwright support planned for v1
|
|
220
220
|
- **Sync only** - No async/await support yet
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|