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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: waitless
3
- Version: 0.3.1
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
- 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
- ╚══════════════════════════════════════════════════════════════════╝
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
- 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
- ╚══════════════════════════════════════════════════════════════════╝
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "waitless"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "Eliminate explicit waits in UI automation by detecting true UI stability"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -36,7 +36,7 @@ Disable:
36
36
  driver = unstabilize(driver) # Back to original behavior
37
37
  """
38
38
 
39
- __version__ = '0.3.1'
39
+ __version__ = '0.3.2'
40
40
  __author__ = 'Dhiraj Das'
41
41
 
42
42
  # Public API
@@ -71,34 +71,34 @@ def run_doctor(args):
71
71
  return 1
72
72
  else:
73
73
  # Show usage instructions
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 + "")
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("" + "" * 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} ")
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("" + "" * 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) + "")
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("" + "" * 66 + "")
43
- lines.append(" BLOCKING FACTORS:".ljust(67) + "")
44
- lines.append("".ljust(67) + "")
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(" NETWORK: {} request(s) still pending".format(pending).ljust(67) + "")
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" {req.get('type', 'unknown').upper()} {url}".ljust(67) + "")
54
+ lines.append(f"| -> {req.get('type', 'unknown').upper()} {url}".ljust(67) + "|")
55
55
 
56
56
  if len(details) > 5:
57
- lines.append(f" ... and {len(details) - 5} more".ljust(67) + "")
58
- lines.append("".ljust(67) + "")
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" ANIMATIONS: {animations} active animation(s)".ljust(67) + "")
63
- lines.append("".ljust(67) + "")
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(" LAYOUT: Elements are still moving".ljust(67) + "")
67
- lines.append("".ljust(67) + "")
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("" + "" * 66 + "")
72
- lines.append(" SIGNAL STATUS:".ljust(67) + "")
71
+ lines.append("+-" + "-" * 66 + "-+")
72
+ lines.append("| SIGNAL STATUS:".ljust(67) + "|")
73
73
 
74
74
  for signal in status.get('signals', []):
75
- state = "" if signal['state'] == 'STABLE' else ""
75
+ state = "[OK]" if signal['state'] == 'STABLE' else "[WAIT]"
76
76
  mandatory = "[M]" if signal['mandatory'] else "[O]"
77
- line = f" {state} {mandatory} {signal['type']}: {signal.get('details', 'N/A')}"
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("" + "" * 66 + "")
83
- lines.append(" RECENT EVENTS (last 10):".ljust(67) + "")
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" [{time_str}] {msg}".ljust(67) + "")
88
+ lines.append(f"| [{time_str}] {msg}".ljust(67) + "|")
89
89
 
90
90
  lines.extend(self._generate_suggestions())
91
- lines.append("" + "" * 66 + "")
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
- rate_stable = mutation_rate <= threshold
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.1
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
- 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
- ╚══════════════════════════════════════════════════════════════════╝
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