Skip to main content

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=5,                    # Max wait time (seconds)
    dom_settle_time=0.1,          # DOM quiet period needed
    network_idle_threshold=0,     # Max pending requests (0 = all must complete)
    animation_detection=True,     # Wait for animations to finish
    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) returns False
  • 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

waitless-0.1.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

waitless-0.1.0-py3-none-any.whl (22.1 kB view details)

Uploaded Python 3

File details

Details for the file waitless-0.1.0.tar.gz.

File metadata

  • Download URL: waitless-0.1.0.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for waitless-0.1.0.tar.gz
Algorithm Hash digest
SHA256 589216bacc3ec8edcee1cd398395073e10127647bfb0876b0754319d521bbaec
MD5 cac9c66e923badfb90c01cd6acf7c0e9
BLAKE2b-256 ac303606fc4705bb9bb24d9a7f9912566e2071a56e3423b01d23a4b62ed0ab2f

See more details on using hashes here.

File details

Details for the file waitless-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: waitless-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 22.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for waitless-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6f5c1b49adbdc24ed8419f77f5b9b637423cdecc680f32bccefab090524bdfe4
MD5 fac5e5c2acc17fb0c6db633f08057805
BLAKE2b-256 6195d78123446ac75f5867777d6b518ebb6609ff4ce3a4fe927bc6f0129ecc95

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page