Eliminate explicit waits in UI automation by detecting true UI stability
Project description
Waitless
Zero-wait UI automation stabilization for Selenium
Eliminate explicit waits and sleeps by automatically detecting true UI stability.
Installation
pip install waitless
Quick Start
from selenium import webdriver
from selenium.webdriver.common.by import By
from waitless import stabilize
# Create driver as usual
driver = webdriver.Chrome()
# Enable automatic stabilization - ONE LINE
driver = stabilize(driver)
# All interactions now auto-wait for stability
driver.get("https://example.com")
driver.find_element(By.ID, "login-button").click() # ← Auto-waits!
driver.find_element(By.ID, "username").send_keys("user") # ← Auto-waits!
Why Waitless?
The Problem
Automation tests fail because interactions happen while the UI is still changing:
- DOM mutations from React/Vue/Angular updates
- In-flight AJAX requests
- CSS animations and transitions
- Layout shifts from lazy-loaded content
Traditional Solutions (and why they fail)
| Approach | Problem |
|---|---|
time.sleep(2) |
Too slow, still fails sometimes |
WebDriverWait |
Only checks one element, misses page-wide state |
| Retries | Masks the real problem, adds flakiness |
The Waitless Solution
Waitless monitors the entire page for stability signals:
- ✅ DOM mutation activity (MutationObserver)
- ✅ Pending network requests (XHR/fetch interception)
- ✅ CSS animations and transitions
- ✅ Layout stability (element movement)
When you interact, waitless ensures the page is truly ready.
Configuration
from waitless import stabilize, StabilizationConfig
config = StabilizationConfig(
timeout=10, # Max wait time (seconds)
mutation_rate_threshold=50, # mutations/sec considered stable (allows animations)
network_idle_threshold=2, # Max pending requests (allows background traffic)
animation_detection=True, # Track CSS animations (non-blocking in normal mode)
strictness='normal', # 'strict' | 'normal' | 'relaxed'
debug_mode=True # Enable logging
)
driver = stabilize(driver, config=config)
Strictness Levels
| Level | What It Waits For |
|---|---|
strict |
DOM + Network + Animations + Layout |
normal |
DOM + Network (default) |
relaxed |
DOM only |
Factory Methods
# For strict testing
config = StabilizationConfig.strict()
# For apps with background traffic
config = StabilizationConfig.relaxed()
# For CI environments
config = StabilizationConfig.ci()
Manual Stabilization
If you don't want to wrap the driver:
from waitless import wait_for_stability
wait_for_stability(driver)
driver.find_element(By.ID, "button").click()
Disabling Stabilization
from waitless import unstabilize
driver = unstabilize(driver) # Back to original behavior
Diagnostics
When tests fail, get detailed analysis:
from waitless import get_diagnostics, StabilizationTimeout
from waitless.diagnostics import print_report
try:
driver.find_element(By.ID, "slow-button").click()
except StabilizationTimeout as e:
diagnostics = get_diagnostics(driver)
print_report(engine) # Print detailed report
CLI Doctor Command
python -m waitless doctor --file diagnostics.json
Sample output:
╔══════════════════════════════════════════════════════════════════╗
║ WAITLESS STABILITY REPORT ║
╠══════════════════════════════════════════════════════════════════╣
║ BLOCKING FACTORS: ║
║ ⚠ NETWORK: 2 request(s) still pending ║
║ → GET /api/users ║
║ ⚠ ANIMATIONS: 1 active animation(s) ║
╠══════════════════════════════════════════════════════════════════╣
║ SUGGESTIONS: ║
║ 1. Set network_idle_threshold=2 for background traffic ║
║ 2. Use animation_detection=False for infinite spinners ║
╚══════════════════════════════════════════════════════════════════╝
Important Notes
Network Threshold Warning
The default network_idle_threshold=0 means all network requests must complete.
Many apps have background traffic that never stops:
- Analytics calls
- Long polling
- Feature flags
- WebSocket heartbeats
If tests timeout frequently, try:
config = StabilizationConfig(network_idle_threshold=2)
Wrapped Elements
The stabilized driver returns wrapped elements that auto-wait. They behave like WebElements but:
isinstance(element, WebElement)returnsFalse- Use
.unwrap()to get the original element if needed
element = driver.find_element(By.ID, "button")
original = element.unwrap() # Gets the real WebElement
v0 Limitations
- Selenium only - Playwright support planned for v1
- Sync only - No async/await support yet
- Main frame only - iframes not monitored
- No Shadow DOM - MutationObserver doesn't see shadow roots
- No Service Workers - SW network requests not intercepted
API Reference
Functions
| Function | Description |
|---|---|
stabilize(driver, config=None) |
Enable auto-stabilization |
unstabilize(driver) |
Disable and return original driver |
wait_for_stability(driver, timeout=None) |
Manual one-time wait |
get_diagnostics(driver) |
Get diagnostic data |
Classes
| Class | Description |
|---|---|
StabilizationConfig |
Configuration options |
StabilizedWebDriver |
Wrapped driver with auto-wait |
StabilizedWebElement |
Wrapped element with auto-wait |
StabilizationTimeout |
Exception when UI doesn't stabilize |
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file waitless-0.2.0.tar.gz.
File metadata
- Download URL: waitless-0.2.0.tar.gz
- Upload date:
- Size: 22.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
750f4ca7d3f4d29c9a08cccb883e7c06c29fb997b15534b53d2cbec62e68d729
|
|
| MD5 |
071c421bd3c05e0b0c500ff791752327
|
|
| BLAKE2b-256 |
807fb16a2348a34183cf71c933b86035078ebcbe724a2985aba73f5324a83b08
|
File details
Details for the file waitless-0.2.0-py3-none-any.whl.
File metadata
- Download URL: waitless-0.2.0-py3-none-any.whl
- Upload date:
- Size: 24.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0408d09bae93444b2935d9cfcd284c340c8d0b781be94f5792c417957f6c0a9b
|
|
| MD5 |
97cc40c3105b273fa716978486809725
|
|
| BLAKE2b-256 |
107a95eea926dbeca993c3987eec6717fce12d5d3f173e93d532bb202f259fef
|