waitless 1.0.2__tar.gz → 1.0.3__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-1.0.2/waitless.egg-info → waitless-1.0.3}/PKG-INFO +39 -38
- {waitless-1.0.2 → waitless-1.0.3}/README.md +34 -33
- {waitless-1.0.2 → waitless-1.0.3}/pyproject.toml +5 -5
- {waitless-1.0.2 → waitless-1.0.3}/waitless/__init__.py +4 -4
- {waitless-1.0.2 → waitless-1.0.3}/waitless/__main__.py +1 -1
- {waitless-1.0.2 → waitless-1.0.3}/waitless/config.py +2 -1
- {waitless-1.0.2 → waitless-1.0.3}/waitless/instrumentation.py +11 -3
- {waitless-1.0.2 → waitless-1.0.3/waitless.egg-info}/PKG-INFO +39 -38
- {waitless-1.0.2 → waitless-1.0.3}/LICENSE +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/setup.cfg +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/adapters/__init__.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/adapters/angular.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/adapters/base.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/adapters/react.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/adapters/vue.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/diagnostics.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/engine.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/exceptions.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/selenium_integration.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless/signals.py +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless.egg-info/SOURCES.txt +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless.egg-info/dependency_links.txt +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless.egg-info/entry_points.txt +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless.egg-info/requires.txt +0 -0
- {waitless-1.0.2 → waitless-1.0.3}/waitless.egg-info/top_level.txt +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: waitless
|
|
3
|
-
Version: 1.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Reduce explicit waits in Selenium by evaluating UI stability signals
|
|
5
5
|
Author-email: Dhiraj Das <dhirajdas.666@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://www.dhirajdas.dev
|
|
8
|
-
Project-URL: Documentation, https://
|
|
9
|
-
Project-URL: Repository, https://github.com/godhiraj-code/waitless
|
|
7
|
+
Project-URL: Homepage, https://www.dhirajdas.dev
|
|
8
|
+
Project-URL: Documentation, https://github.com/godhiraj-code/waitless#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/godhiraj-code/waitless.git
|
|
10
10
|
Project-URL: Issues, https://github.com/godhiraj-code/waitless/issues
|
|
11
11
|
Keywords: selenium,automation,testing,ui-testing,wait,stability,flaky-tests
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -36,9 +36,7 @@ Dynamic: license-file
|
|
|
36
36
|
|
|
37
37
|
Reduce explicit waits and sleeps by automatically evaluating multiple UI stability signals.
|
|
38
38
|
|
|
39
|
-
[Watch the demo and read the architecture case study](https://www.dhirajdas.dev/project/waitless) · [Install from PyPI](https://pypi.org/project/waitless/)
|
|
40
39
|
|
|
41
|
-

|
|
42
40
|
|
|
43
41
|
## Installation
|
|
44
42
|
|
|
@@ -53,16 +51,16 @@ from selenium import webdriver
|
|
|
53
51
|
from selenium.webdriver.common.by import By
|
|
54
52
|
from waitless import stabilize
|
|
55
53
|
|
|
56
|
-
# Create driver as usual
|
|
57
54
|
driver = webdriver.Chrome()
|
|
58
|
-
|
|
59
|
-
# Enable automatic stabilization - ONE LINE
|
|
60
55
|
driver = stabilize(driver)
|
|
61
56
|
|
|
62
|
-
#
|
|
63
|
-
driver.get(
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
# Navigation, lookups, and common element actions now wait for stability.
|
|
58
|
+
driver.get(
|
|
59
|
+
"data:text/html,<input id='username'><button id='login-button'>Log in</button>"
|
|
60
|
+
)
|
|
61
|
+
driver.find_element(By.ID, "username").send_keys("user")
|
|
62
|
+
driver.find_element(By.ID, "login-button").click()
|
|
63
|
+
driver.quit()
|
|
66
64
|
```
|
|
67
65
|
|
|
68
66
|
## Why Waitless?
|
|
@@ -81,22 +79,23 @@ Automation tests fail because interactions happen while the UI is still changing
|
|
|
81
79
|
| Approach | Problem |
|
|
82
80
|
|----------|---------|
|
|
83
81
|
| `time.sleep(2)` | Too slow, still fails sometimes |
|
|
84
|
-
| `WebDriverWait` |
|
|
82
|
+
| `WebDriverWait` | Requires an explicit condition at each synchronization point |
|
|
85
83
|
| Retries | Masks the real problem, adds flakiness |
|
|
86
84
|
|
|
87
85
|
### The Waitless Solution
|
|
88
86
|
|
|
89
|
-
Waitless
|
|
87
|
+
Waitless evaluates page-level stability signals:
|
|
90
88
|
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
89
|
+
- DOM mutation activity (MutationObserver, including **Shadow DOM**)
|
|
90
|
+
- Pending network requests (XHR/fetch interception after instrumentation is installed)
|
|
91
|
+
- CSS animations and transitions
|
|
92
|
+
- Layout stability for interactive elements
|
|
93
|
+
- WebSocket/SSE activity (opt-in)
|
|
94
|
+
- Framework hooks (React/Angular/Vue, opt-in)
|
|
95
|
+
- Same-origin iframe load readiness (opt-in; not full child-frame signal injection)
|
|
98
96
|
|
|
99
|
-
|
|
97
|
+
Before supported interactions, Waitless polls until the enabled mandatory signals
|
|
98
|
+
meet their configured thresholds.
|
|
100
99
|
|
|
101
100
|
## Configuration
|
|
102
101
|
|
|
@@ -204,14 +203,15 @@ Many apps have background traffic that never stops:
|
|
|
204
203
|
- Feature flags
|
|
205
204
|
- WebSocket heartbeats
|
|
206
205
|
|
|
207
|
-
If
|
|
206
|
+
If known background traffic exceeds the default, raise the threshold carefully:
|
|
208
207
|
```python
|
|
209
|
-
config = StabilizationConfig(network_idle_threshold=
|
|
208
|
+
config = StabilizationConfig(network_idle_threshold=3)
|
|
210
209
|
```
|
|
211
210
|
|
|
212
211
|
### Wrapped Elements
|
|
213
212
|
|
|
214
|
-
The stabilized driver returns wrapped elements that auto-wait
|
|
213
|
+
The stabilized driver returns wrapped elements that auto-wait before `click()`,
|
|
214
|
+
`send_keys()`, `submit()`, and `clear()`. They behave like WebElements but:
|
|
215
215
|
|
|
216
216
|
- `isinstance(element, WebElement)` returns `False`
|
|
217
217
|
- Use `.unwrap()` to get the original element if needed
|
|
@@ -221,15 +221,14 @@ element = driver.find_element(By.ID, "button")
|
|
|
221
221
|
original = element.unwrap() # Gets the real WebElement
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
##
|
|
224
|
+
## Optional Signals
|
|
225
225
|
|
|
226
226
|
- **WebSocket/SSE Awareness** - Track WebSocket and Server-Sent Events activity
|
|
227
227
|
- **Framework Adapters** - React, Angular, Vue hooks for framework-specific settling
|
|
228
228
|
- **iframe Support** - Monitor same-origin iframes
|
|
229
|
-
- **Performance
|
|
229
|
+
- **Performance benchmarks** - Run the repository benchmark against your environment
|
|
230
230
|
|
|
231
231
|
```python
|
|
232
|
-
# Enable new v1.0 features
|
|
233
232
|
config = StabilizationConfig(
|
|
234
233
|
track_websocket=True, # WebSocket monitoring
|
|
235
234
|
track_sse=True, # SSE monitoring
|
|
@@ -242,10 +241,10 @@ config = StabilizationConfig(
|
|
|
242
241
|
|
|
243
242
|
| Metric | Typical Value |
|
|
244
243
|
|--------|---------------|
|
|
245
|
-
| Instrumentation injection | Environment-dependent; run `python
|
|
246
|
-
| Per-poll overhead | Environment-dependent; run the
|
|
244
|
+
| Instrumentation injection | Environment-dependent; from a repository checkout, run `python benchmarks/overhead_test.py` |
|
|
245
|
+
| Per-poll overhead | Environment-dependent; run the repository benchmark |
|
|
247
246
|
| Poll interval (default) | 50ms |
|
|
248
|
-
|
|
|
247
|
+
| Stabilization time | Depends on page activity, thresholds, and environment |
|
|
249
248
|
|
|
250
249
|
### Navigation Handling
|
|
251
250
|
|
|
@@ -260,12 +259,18 @@ JavaScript, Waitless validates/re-injects instrumentation on the next wait:
|
|
|
260
259
|
This does not observe routes continuously, and cross-origin iframe internals remain
|
|
261
260
|
outside the browser same-origin boundary.
|
|
262
261
|
|
|
262
|
+
Because instrumentation is installed after Selenium's synchronous navigation call
|
|
263
|
+
returns, requests that start and finish during navigation are not observed. Requests
|
|
264
|
+
started after instrumentation is installed are tracked.
|
|
265
|
+
|
|
263
266
|
`find_elements()` keeps Selenium's immediate-empty lookup semantics: after the
|
|
264
267
|
page-stability wait, it performs one lookup and returns `[]` when there are no matches.
|
|
268
|
+
By contrast, `find_element()` retries `NoSuchElementException` until the configured
|
|
269
|
+
timeout after the page-stability wait.
|
|
265
270
|
|
|
266
271
|
## Current Limitations
|
|
267
272
|
|
|
268
|
-
- **Selenium only** - Playwright
|
|
273
|
+
- **Selenium only** - No Playwright integration
|
|
269
274
|
- **Sync only** - No async/await support yet
|
|
270
275
|
- **No Service Workers** - SW network requests not intercepted
|
|
271
276
|
|
|
@@ -294,7 +299,3 @@ See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
|
294
299
|
## License
|
|
295
300
|
|
|
296
301
|
MIT
|
|
297
|
-
|
|
298
|
-
## Try It on a Real Flaky Flow
|
|
299
|
-
|
|
300
|
-
Run Waitless against one Selenium flow that currently uses sleeps or retries, then compare failures and elapsed time before and after. Share a sanitized result in an issue. If it removes a sleep or retry, [star the repository](https://github.com/godhiraj-code/waitless). The [full case study](https://www.dhirajdas.dev/project/waitless) explains the architecture and trade-offs.
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Reduce explicit waits and sleeps by automatically evaluating multiple UI stability signals.
|
|
8
8
|
|
|
9
|
-
[Watch the demo and read the architecture case study](https://www.dhirajdas.dev/project/waitless) · [Install from PyPI](https://pypi.org/project/waitless/)
|
|
10
9
|
|
|
11
|
-

|
|
12
10
|
|
|
13
11
|
## Installation
|
|
14
12
|
|
|
@@ -23,16 +21,16 @@ from selenium import webdriver
|
|
|
23
21
|
from selenium.webdriver.common.by import By
|
|
24
22
|
from waitless import stabilize
|
|
25
23
|
|
|
26
|
-
# Create driver as usual
|
|
27
24
|
driver = webdriver.Chrome()
|
|
28
|
-
|
|
29
|
-
# Enable automatic stabilization - ONE LINE
|
|
30
25
|
driver = stabilize(driver)
|
|
31
26
|
|
|
32
|
-
#
|
|
33
|
-
driver.get(
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
# Navigation, lookups, and common element actions now wait for stability.
|
|
28
|
+
driver.get(
|
|
29
|
+
"data:text/html,<input id='username'><button id='login-button'>Log in</button>"
|
|
30
|
+
)
|
|
31
|
+
driver.find_element(By.ID, "username").send_keys("user")
|
|
32
|
+
driver.find_element(By.ID, "login-button").click()
|
|
33
|
+
driver.quit()
|
|
36
34
|
```
|
|
37
35
|
|
|
38
36
|
## Why Waitless?
|
|
@@ -51,22 +49,23 @@ Automation tests fail because interactions happen while the UI is still changing
|
|
|
51
49
|
| Approach | Problem |
|
|
52
50
|
|----------|---------|
|
|
53
51
|
| `time.sleep(2)` | Too slow, still fails sometimes |
|
|
54
|
-
| `WebDriverWait` |
|
|
52
|
+
| `WebDriverWait` | Requires an explicit condition at each synchronization point |
|
|
55
53
|
| Retries | Masks the real problem, adds flakiness |
|
|
56
54
|
|
|
57
55
|
### The Waitless Solution
|
|
58
56
|
|
|
59
|
-
Waitless
|
|
57
|
+
Waitless evaluates page-level stability signals:
|
|
60
58
|
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
59
|
+
- DOM mutation activity (MutationObserver, including **Shadow DOM**)
|
|
60
|
+
- Pending network requests (XHR/fetch interception after instrumentation is installed)
|
|
61
|
+
- CSS animations and transitions
|
|
62
|
+
- Layout stability for interactive elements
|
|
63
|
+
- WebSocket/SSE activity (opt-in)
|
|
64
|
+
- Framework hooks (React/Angular/Vue, opt-in)
|
|
65
|
+
- Same-origin iframe load readiness (opt-in; not full child-frame signal injection)
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
Before supported interactions, Waitless polls until the enabled mandatory signals
|
|
68
|
+
meet their configured thresholds.
|
|
70
69
|
|
|
71
70
|
## Configuration
|
|
72
71
|
|
|
@@ -174,14 +173,15 @@ Many apps have background traffic that never stops:
|
|
|
174
173
|
- Feature flags
|
|
175
174
|
- WebSocket heartbeats
|
|
176
175
|
|
|
177
|
-
If
|
|
176
|
+
If known background traffic exceeds the default, raise the threshold carefully:
|
|
178
177
|
```python
|
|
179
|
-
config = StabilizationConfig(network_idle_threshold=
|
|
178
|
+
config = StabilizationConfig(network_idle_threshold=3)
|
|
180
179
|
```
|
|
181
180
|
|
|
182
181
|
### Wrapped Elements
|
|
183
182
|
|
|
184
|
-
The stabilized driver returns wrapped elements that auto-wait
|
|
183
|
+
The stabilized driver returns wrapped elements that auto-wait before `click()`,
|
|
184
|
+
`send_keys()`, `submit()`, and `clear()`. They behave like WebElements but:
|
|
185
185
|
|
|
186
186
|
- `isinstance(element, WebElement)` returns `False`
|
|
187
187
|
- Use `.unwrap()` to get the original element if needed
|
|
@@ -191,15 +191,14 @@ element = driver.find_element(By.ID, "button")
|
|
|
191
191
|
original = element.unwrap() # Gets the real WebElement
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
##
|
|
194
|
+
## Optional Signals
|
|
195
195
|
|
|
196
196
|
- **WebSocket/SSE Awareness** - Track WebSocket and Server-Sent Events activity
|
|
197
197
|
- **Framework Adapters** - React, Angular, Vue hooks for framework-specific settling
|
|
198
198
|
- **iframe Support** - Monitor same-origin iframes
|
|
199
|
-
- **Performance
|
|
199
|
+
- **Performance benchmarks** - Run the repository benchmark against your environment
|
|
200
200
|
|
|
201
201
|
```python
|
|
202
|
-
# Enable new v1.0 features
|
|
203
202
|
config = StabilizationConfig(
|
|
204
203
|
track_websocket=True, # WebSocket monitoring
|
|
205
204
|
track_sse=True, # SSE monitoring
|
|
@@ -212,10 +211,10 @@ config = StabilizationConfig(
|
|
|
212
211
|
|
|
213
212
|
| Metric | Typical Value |
|
|
214
213
|
|--------|---------------|
|
|
215
|
-
| Instrumentation injection | Environment-dependent; run `python
|
|
216
|
-
| Per-poll overhead | Environment-dependent; run the
|
|
214
|
+
| Instrumentation injection | Environment-dependent; from a repository checkout, run `python benchmarks/overhead_test.py` |
|
|
215
|
+
| Per-poll overhead | Environment-dependent; run the repository benchmark |
|
|
217
216
|
| Poll interval (default) | 50ms |
|
|
218
|
-
|
|
|
217
|
+
| Stabilization time | Depends on page activity, thresholds, and environment |
|
|
219
218
|
|
|
220
219
|
### Navigation Handling
|
|
221
220
|
|
|
@@ -230,12 +229,18 @@ JavaScript, Waitless validates/re-injects instrumentation on the next wait:
|
|
|
230
229
|
This does not observe routes continuously, and cross-origin iframe internals remain
|
|
231
230
|
outside the browser same-origin boundary.
|
|
232
231
|
|
|
232
|
+
Because instrumentation is installed after Selenium's synchronous navigation call
|
|
233
|
+
returns, requests that start and finish during navigation are not observed. Requests
|
|
234
|
+
started after instrumentation is installed are tracked.
|
|
235
|
+
|
|
233
236
|
`find_elements()` keeps Selenium's immediate-empty lookup semantics: after the
|
|
234
237
|
page-stability wait, it performs one lookup and returns `[]` when there are no matches.
|
|
238
|
+
By contrast, `find_element()` retries `NoSuchElementException` until the configured
|
|
239
|
+
timeout after the page-stability wait.
|
|
235
240
|
|
|
236
241
|
## Current Limitations
|
|
237
242
|
|
|
238
|
-
- **Selenium only** - Playwright
|
|
243
|
+
- **Selenium only** - No Playwright integration
|
|
239
244
|
- **Sync only** - No async/await support yet
|
|
240
245
|
- **No Service Workers** - SW network requests not intercepted
|
|
241
246
|
|
|
@@ -264,7 +269,3 @@ See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
|
264
269
|
## License
|
|
265
270
|
|
|
266
271
|
MIT
|
|
267
|
-
|
|
268
|
-
## Try It on a Real Flaky Flow
|
|
269
|
-
|
|
270
|
-
Run Waitless against one Selenium flow that currently uses sleeps or retries, then compare failures and elapsed time before and after. Share a sanitized result in an issue. If it removes a sleep or retry, [star the repository](https://github.com/godhiraj-code/waitless). The [full case study](https://www.dhirajdas.dev/project/waitless) explains the architecture and trade-offs.
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "waitless"
|
|
7
|
-
version = "1.0.
|
|
8
|
-
description = "
|
|
7
|
+
version = "1.0.3"
|
|
8
|
+
description = "Reduce explicit waits in Selenium by evaluating UI stability signals"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
requires-python = ">=3.9"
|
|
@@ -38,9 +38,9 @@ dependencies = [
|
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[project.urls]
|
|
41
|
-
Homepage = "https://www.dhirajdas.dev
|
|
42
|
-
Documentation = "https://
|
|
43
|
-
Repository = "https://github.com/godhiraj-code/waitless"
|
|
41
|
+
Homepage = "https://www.dhirajdas.dev"
|
|
42
|
+
Documentation = "https://github.com/godhiraj-code/waitless#readme"
|
|
43
|
+
Repository = "https://github.com/godhiraj-code/waitless.git"
|
|
44
44
|
Issues = "https://github.com/godhiraj-code/waitless/issues"
|
|
45
45
|
|
|
46
46
|
[project.scripts]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Waitless -
|
|
2
|
+
Waitless - Automatic Selenium UI stabilization library.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Reduce explicit waits and sleeps by evaluating configurable UI stability
|
|
5
|
+
signals before supported Selenium interactions.
|
|
6
6
|
|
|
7
7
|
Basic Usage:
|
|
8
8
|
from waitless import stabilize
|
|
@@ -36,7 +36,7 @@ Disable:
|
|
|
36
36
|
driver = unstabilize(driver) # Back to original behavior
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
|
-
__version__ = '1.0.
|
|
39
|
+
__version__ = '1.0.3'
|
|
40
40
|
__author__ = 'Dhiraj Das'
|
|
41
41
|
|
|
42
42
|
# Public API
|
|
@@ -14,7 +14,7 @@ def main():
|
|
|
14
14
|
"""Main CLI entry point."""
|
|
15
15
|
parser = argparse.ArgumentParser(
|
|
16
16
|
prog='waitless',
|
|
17
|
-
description='Waitless -
|
|
17
|
+
description='Waitless - Automatic Selenium UI stabilization'
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
|
@@ -66,7 +66,8 @@ class StabilizationConfig:
|
|
|
66
66
|
Options: 'react', 'angular', 'vue'. Default empty (auto-detect off).
|
|
67
67
|
When specified, waitless will inject framework-specific hooks.
|
|
68
68
|
|
|
69
|
-
track_iframes: Whether to
|
|
69
|
+
track_iframes: Whether to monitor same-origin iframe load readiness.
|
|
70
|
+
This does not inject full child-frame instrumentation.
|
|
70
71
|
Default False. Cross-origin iframes cannot be accessed.
|
|
71
72
|
"""
|
|
72
73
|
|
|
@@ -12,7 +12,7 @@ INSTRUMENTATION_SCRIPT = """
|
|
|
12
12
|
|
|
13
13
|
window.__waitless__ = {
|
|
14
14
|
_initialized: true,
|
|
15
|
-
_version: '1.0.
|
|
15
|
+
_version: '1.0.3',
|
|
16
16
|
|
|
17
17
|
// State tracking
|
|
18
18
|
pendingRequests: 0,
|
|
@@ -249,8 +249,16 @@ INSTRUMENTATION_SCRIPT = """
|
|
|
249
249
|
xhr.addEventListener('loadend', function() {
|
|
250
250
|
self._requestEnded(url, 'xhr', xhr.status);
|
|
251
251
|
});
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
return self._originalXHRSend.apply(this, arguments);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
// Synchronous failures (for example, send() before open()) do
|
|
257
|
+
// not emit loadend. Balance the counter before preserving the
|
|
258
|
+
// browser's original exception behavior.
|
|
259
|
+
self._requestEnded(url, 'xhr', 'error');
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
254
262
|
};
|
|
255
263
|
},
|
|
256
264
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: waitless
|
|
3
|
-
Version: 1.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Reduce explicit waits in Selenium by evaluating UI stability signals
|
|
5
5
|
Author-email: Dhiraj Das <dhirajdas.666@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://www.dhirajdas.dev
|
|
8
|
-
Project-URL: Documentation, https://
|
|
9
|
-
Project-URL: Repository, https://github.com/godhiraj-code/waitless
|
|
7
|
+
Project-URL: Homepage, https://www.dhirajdas.dev
|
|
8
|
+
Project-URL: Documentation, https://github.com/godhiraj-code/waitless#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/godhiraj-code/waitless.git
|
|
10
10
|
Project-URL: Issues, https://github.com/godhiraj-code/waitless/issues
|
|
11
11
|
Keywords: selenium,automation,testing,ui-testing,wait,stability,flaky-tests
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -36,9 +36,7 @@ Dynamic: license-file
|
|
|
36
36
|
|
|
37
37
|
Reduce explicit waits and sleeps by automatically evaluating multiple UI stability signals.
|
|
38
38
|
|
|
39
|
-
[Watch the demo and read the architecture case study](https://www.dhirajdas.dev/project/waitless) · [Install from PyPI](https://pypi.org/project/waitless/)
|
|
40
39
|
|
|
41
|
-

|
|
42
40
|
|
|
43
41
|
## Installation
|
|
44
42
|
|
|
@@ -53,16 +51,16 @@ from selenium import webdriver
|
|
|
53
51
|
from selenium.webdriver.common.by import By
|
|
54
52
|
from waitless import stabilize
|
|
55
53
|
|
|
56
|
-
# Create driver as usual
|
|
57
54
|
driver = webdriver.Chrome()
|
|
58
|
-
|
|
59
|
-
# Enable automatic stabilization - ONE LINE
|
|
60
55
|
driver = stabilize(driver)
|
|
61
56
|
|
|
62
|
-
#
|
|
63
|
-
driver.get(
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
# Navigation, lookups, and common element actions now wait for stability.
|
|
58
|
+
driver.get(
|
|
59
|
+
"data:text/html,<input id='username'><button id='login-button'>Log in</button>"
|
|
60
|
+
)
|
|
61
|
+
driver.find_element(By.ID, "username").send_keys("user")
|
|
62
|
+
driver.find_element(By.ID, "login-button").click()
|
|
63
|
+
driver.quit()
|
|
66
64
|
```
|
|
67
65
|
|
|
68
66
|
## Why Waitless?
|
|
@@ -81,22 +79,23 @@ Automation tests fail because interactions happen while the UI is still changing
|
|
|
81
79
|
| Approach | Problem |
|
|
82
80
|
|----------|---------|
|
|
83
81
|
| `time.sleep(2)` | Too slow, still fails sometimes |
|
|
84
|
-
| `WebDriverWait` |
|
|
82
|
+
| `WebDriverWait` | Requires an explicit condition at each synchronization point |
|
|
85
83
|
| Retries | Masks the real problem, adds flakiness |
|
|
86
84
|
|
|
87
85
|
### The Waitless Solution
|
|
88
86
|
|
|
89
|
-
Waitless
|
|
87
|
+
Waitless evaluates page-level stability signals:
|
|
90
88
|
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
89
|
+
- DOM mutation activity (MutationObserver, including **Shadow DOM**)
|
|
90
|
+
- Pending network requests (XHR/fetch interception after instrumentation is installed)
|
|
91
|
+
- CSS animations and transitions
|
|
92
|
+
- Layout stability for interactive elements
|
|
93
|
+
- WebSocket/SSE activity (opt-in)
|
|
94
|
+
- Framework hooks (React/Angular/Vue, opt-in)
|
|
95
|
+
- Same-origin iframe load readiness (opt-in; not full child-frame signal injection)
|
|
98
96
|
|
|
99
|
-
|
|
97
|
+
Before supported interactions, Waitless polls until the enabled mandatory signals
|
|
98
|
+
meet their configured thresholds.
|
|
100
99
|
|
|
101
100
|
## Configuration
|
|
102
101
|
|
|
@@ -204,14 +203,15 @@ Many apps have background traffic that never stops:
|
|
|
204
203
|
- Feature flags
|
|
205
204
|
- WebSocket heartbeats
|
|
206
205
|
|
|
207
|
-
If
|
|
206
|
+
If known background traffic exceeds the default, raise the threshold carefully:
|
|
208
207
|
```python
|
|
209
|
-
config = StabilizationConfig(network_idle_threshold=
|
|
208
|
+
config = StabilizationConfig(network_idle_threshold=3)
|
|
210
209
|
```
|
|
211
210
|
|
|
212
211
|
### Wrapped Elements
|
|
213
212
|
|
|
214
|
-
The stabilized driver returns wrapped elements that auto-wait
|
|
213
|
+
The stabilized driver returns wrapped elements that auto-wait before `click()`,
|
|
214
|
+
`send_keys()`, `submit()`, and `clear()`. They behave like WebElements but:
|
|
215
215
|
|
|
216
216
|
- `isinstance(element, WebElement)` returns `False`
|
|
217
217
|
- Use `.unwrap()` to get the original element if needed
|
|
@@ -221,15 +221,14 @@ element = driver.find_element(By.ID, "button")
|
|
|
221
221
|
original = element.unwrap() # Gets the real WebElement
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
##
|
|
224
|
+
## Optional Signals
|
|
225
225
|
|
|
226
226
|
- **WebSocket/SSE Awareness** - Track WebSocket and Server-Sent Events activity
|
|
227
227
|
- **Framework Adapters** - React, Angular, Vue hooks for framework-specific settling
|
|
228
228
|
- **iframe Support** - Monitor same-origin iframes
|
|
229
|
-
- **Performance
|
|
229
|
+
- **Performance benchmarks** - Run the repository benchmark against your environment
|
|
230
230
|
|
|
231
231
|
```python
|
|
232
|
-
# Enable new v1.0 features
|
|
233
232
|
config = StabilizationConfig(
|
|
234
233
|
track_websocket=True, # WebSocket monitoring
|
|
235
234
|
track_sse=True, # SSE monitoring
|
|
@@ -242,10 +241,10 @@ config = StabilizationConfig(
|
|
|
242
241
|
|
|
243
242
|
| Metric | Typical Value |
|
|
244
243
|
|--------|---------------|
|
|
245
|
-
| Instrumentation injection | Environment-dependent; run `python
|
|
246
|
-
| Per-poll overhead | Environment-dependent; run the
|
|
244
|
+
| Instrumentation injection | Environment-dependent; from a repository checkout, run `python benchmarks/overhead_test.py` |
|
|
245
|
+
| Per-poll overhead | Environment-dependent; run the repository benchmark |
|
|
247
246
|
| Poll interval (default) | 50ms |
|
|
248
|
-
|
|
|
247
|
+
| Stabilization time | Depends on page activity, thresholds, and environment |
|
|
249
248
|
|
|
250
249
|
### Navigation Handling
|
|
251
250
|
|
|
@@ -260,12 +259,18 @@ JavaScript, Waitless validates/re-injects instrumentation on the next wait:
|
|
|
260
259
|
This does not observe routes continuously, and cross-origin iframe internals remain
|
|
261
260
|
outside the browser same-origin boundary.
|
|
262
261
|
|
|
262
|
+
Because instrumentation is installed after Selenium's synchronous navigation call
|
|
263
|
+
returns, requests that start and finish during navigation are not observed. Requests
|
|
264
|
+
started after instrumentation is installed are tracked.
|
|
265
|
+
|
|
263
266
|
`find_elements()` keeps Selenium's immediate-empty lookup semantics: after the
|
|
264
267
|
page-stability wait, it performs one lookup and returns `[]` when there are no matches.
|
|
268
|
+
By contrast, `find_element()` retries `NoSuchElementException` until the configured
|
|
269
|
+
timeout after the page-stability wait.
|
|
265
270
|
|
|
266
271
|
## Current Limitations
|
|
267
272
|
|
|
268
|
-
- **Selenium only** - Playwright
|
|
273
|
+
- **Selenium only** - No Playwright integration
|
|
269
274
|
- **Sync only** - No async/await support yet
|
|
270
275
|
- **No Service Workers** - SW network requests not intercepted
|
|
271
276
|
|
|
@@ -294,7 +299,3 @@ See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
|
294
299
|
## License
|
|
295
300
|
|
|
296
301
|
MIT
|
|
297
|
-
|
|
298
|
-
## Try It on a Real Flaky Flow
|
|
299
|
-
|
|
300
|
-
Run Waitless against one Selenium flow that currently uses sleeps or retries, then compare failures and elapsed time before and after. Share a sanitized result in an issue. If it removes a sleep or retry, [star the repository](https://github.com/godhiraj-code/waitless). The [full case study](https://www.dhirajdas.dev/project/waitless) explains the architecture and trade-offs.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|