waitless 0.1.0__py3-none-any.whl
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/__init__.py +92 -0
- waitless/__main__.py +116 -0
- waitless/config.py +162 -0
- waitless/diagnostics.py +171 -0
- waitless/engine.py +275 -0
- waitless/exceptions.py +108 -0
- waitless/instrumentation.py +328 -0
- waitless/selenium_integration.py +296 -0
- waitless/signals.py +215 -0
- waitless-0.1.0.dist-info/METADATA +243 -0
- waitless-0.1.0.dist-info/RECORD +14 -0
- waitless-0.1.0.dist-info/WHEEL +5 -0
- waitless-0.1.0.dist-info/entry_points.txt +2 -0
- waitless-0.1.0.dist-info/top_level.txt +1 -0
waitless/signals.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stability signal definitions and combination logic.
|
|
3
|
+
|
|
4
|
+
Signals are measurable indicators of UI state. This module defines
|
|
5
|
+
what signals exist, how to interpret them, and how to combine them
|
|
6
|
+
into an overall stability decision.
|
|
7
|
+
|
|
8
|
+
Note: This module is designed to be a future extension point.
|
|
9
|
+
Users may eventually be able to define custom signals.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from enum import Enum, auto
|
|
14
|
+
from typing import Dict, Any, List, Optional
|
|
15
|
+
from .config import StabilizationConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SignalType(Enum):
|
|
19
|
+
"""Types of stability signals we monitor."""
|
|
20
|
+
DOM_MUTATIONS = auto()
|
|
21
|
+
NETWORK_REQUESTS = auto()
|
|
22
|
+
CSS_ANIMATIONS = auto()
|
|
23
|
+
CSS_TRANSITIONS = auto()
|
|
24
|
+
LAYOUT_SHIFT = auto()
|
|
25
|
+
RAF_ACTIVITY = auto()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SignalState(Enum):
|
|
29
|
+
"""State of an individual signal."""
|
|
30
|
+
STABLE = auto() # Signal indicates stability
|
|
31
|
+
UNSTABLE = auto() # Signal indicates instability
|
|
32
|
+
UNKNOWN = auto() # Signal not yet measured
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Signal:
|
|
37
|
+
"""
|
|
38
|
+
Represents a single stability signal measurement.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
signal_type: The type of signal
|
|
42
|
+
state: Current state (stable/unstable/unknown)
|
|
43
|
+
value: Raw measurement value
|
|
44
|
+
threshold: Threshold used for stability decision
|
|
45
|
+
is_mandatory: Whether this signal must be stable
|
|
46
|
+
details: Additional diagnostic information
|
|
47
|
+
"""
|
|
48
|
+
signal_type: SignalType
|
|
49
|
+
state: SignalState
|
|
50
|
+
value: Any
|
|
51
|
+
threshold: Any
|
|
52
|
+
is_mandatory: bool
|
|
53
|
+
details: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def is_stable(self) -> bool:
|
|
57
|
+
return self.state == SignalState.STABLE
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def is_blocking(self) -> bool:
|
|
61
|
+
"""Returns True if this signal is blocking overall stability."""
|
|
62
|
+
return self.is_mandatory and not self.is_stable
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class StabilityStatus:
|
|
67
|
+
"""
|
|
68
|
+
Overall stability status combining all signals.
|
|
69
|
+
|
|
70
|
+
Attributes:
|
|
71
|
+
is_stable: Whether UI is considered stable
|
|
72
|
+
signals: Individual signal measurements
|
|
73
|
+
blocking_signals: Signals preventing stability
|
|
74
|
+
timestamp: When this status was measured
|
|
75
|
+
"""
|
|
76
|
+
is_stable: bool
|
|
77
|
+
signals: List[Signal]
|
|
78
|
+
timestamp: float
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def blocking_signals(self) -> List[Signal]:
|
|
82
|
+
"""Get list of signals blocking stability."""
|
|
83
|
+
return [s for s in self.signals if s.is_blocking]
|
|
84
|
+
|
|
85
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
86
|
+
"""Convert to dictionary for diagnostics."""
|
|
87
|
+
return {
|
|
88
|
+
'is_stable': self.is_stable,
|
|
89
|
+
'timestamp': self.timestamp,
|
|
90
|
+
'signals': [
|
|
91
|
+
{
|
|
92
|
+
'type': s.signal_type.name,
|
|
93
|
+
'state': s.state.name,
|
|
94
|
+
'value': s.value,
|
|
95
|
+
'threshold': s.threshold,
|
|
96
|
+
'mandatory': s.is_mandatory,
|
|
97
|
+
'details': s.details,
|
|
98
|
+
}
|
|
99
|
+
for s in self.signals
|
|
100
|
+
],
|
|
101
|
+
'blocking': [s.signal_type.name for s in self.blocking_signals],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class SignalEvaluator:
|
|
106
|
+
"""
|
|
107
|
+
Evaluates browser state against configured thresholds.
|
|
108
|
+
|
|
109
|
+
This class interprets raw browser measurements and converts them
|
|
110
|
+
into Signal objects with stability decisions.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, config: StabilizationConfig):
|
|
114
|
+
self.config = config
|
|
115
|
+
|
|
116
|
+
def evaluate(self, browser_state: Dict[str, Any], current_time: float) -> StabilityStatus:
|
|
117
|
+
"""
|
|
118
|
+
Evaluate browser state and return stability status.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
browser_state: Raw state from JavaScript instrumentation
|
|
122
|
+
current_time: Current timestamp for calculations
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
StabilityStatus with all signal evaluations
|
|
126
|
+
"""
|
|
127
|
+
signals = []
|
|
128
|
+
|
|
129
|
+
dom_signal = self._evaluate_dom(browser_state, current_time)
|
|
130
|
+
signals.append(dom_signal)
|
|
131
|
+
|
|
132
|
+
network_signal = self._evaluate_network(browser_state)
|
|
133
|
+
signals.append(network_signal)
|
|
134
|
+
|
|
135
|
+
if self.config.animation_detection:
|
|
136
|
+
animation_signal = self._evaluate_animations(browser_state)
|
|
137
|
+
signals.append(animation_signal)
|
|
138
|
+
|
|
139
|
+
if self.config.strictness == 'strict' and self.config.layout_stability:
|
|
140
|
+
layout_signal = self._evaluate_layout(browser_state)
|
|
141
|
+
signals.append(layout_signal)
|
|
142
|
+
|
|
143
|
+
is_stable = all(
|
|
144
|
+
s.is_stable for s in signals if s.is_mandatory
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if self.config.strictness == 'strict' and is_stable:
|
|
148
|
+
is_stable = all(s.is_stable for s in signals)
|
|
149
|
+
|
|
150
|
+
return StabilityStatus(
|
|
151
|
+
is_stable=is_stable,
|
|
152
|
+
signals=signals,
|
|
153
|
+
timestamp=current_time,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _evaluate_dom(self, state: Dict[str, Any], current_time: float) -> Signal:
|
|
157
|
+
"""Evaluate DOM mutation activity."""
|
|
158
|
+
last_mutation = state.get('last_mutation_time', 0)
|
|
159
|
+
time_since_mutation = (current_time * 1000) - last_mutation # Convert to ms
|
|
160
|
+
threshold_ms = self.config.dom_settle_time * 1000
|
|
161
|
+
|
|
162
|
+
is_stable = time_since_mutation >= threshold_ms
|
|
163
|
+
|
|
164
|
+
return Signal(
|
|
165
|
+
signal_type=SignalType.DOM_MUTATIONS,
|
|
166
|
+
state=SignalState.STABLE if is_stable else SignalState.UNSTABLE,
|
|
167
|
+
value=time_since_mutation,
|
|
168
|
+
threshold=threshold_ms,
|
|
169
|
+
is_mandatory=True,
|
|
170
|
+
details=f"Last mutation {time_since_mutation:.0f}ms ago (need {threshold_ms:.0f}ms quiet)",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _evaluate_network(self, state: Dict[str, Any]) -> Signal:
|
|
174
|
+
"""Evaluate pending network requests."""
|
|
175
|
+
pending = state.get('pending_requests', 0)
|
|
176
|
+
threshold = self.config.network_idle_threshold
|
|
177
|
+
|
|
178
|
+
is_stable = pending <= threshold
|
|
179
|
+
|
|
180
|
+
return Signal(
|
|
181
|
+
signal_type=SignalType.NETWORK_REQUESTS,
|
|
182
|
+
state=SignalState.STABLE if is_stable else SignalState.UNSTABLE,
|
|
183
|
+
value=pending,
|
|
184
|
+
threshold=threshold,
|
|
185
|
+
is_mandatory=True,
|
|
186
|
+
details=f"{pending} pending request(s) (threshold: {threshold})",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _evaluate_animations(self, state: Dict[str, Any]) -> Signal:
|
|
190
|
+
"""Evaluate CSS animation/transition activity."""
|
|
191
|
+
active = state.get('active_animations', 0)
|
|
192
|
+
is_mandatory = self.config.strictness != 'relaxed'
|
|
193
|
+
is_stable = active == 0
|
|
194
|
+
|
|
195
|
+
return Signal(
|
|
196
|
+
signal_type=SignalType.CSS_ANIMATIONS,
|
|
197
|
+
state=SignalState.STABLE if is_stable else SignalState.UNSTABLE,
|
|
198
|
+
value=active,
|
|
199
|
+
threshold=0,
|
|
200
|
+
is_mandatory=is_mandatory,
|
|
201
|
+
details=f"{active} active animation(s)",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def _evaluate_layout(self, state: Dict[str, Any]) -> Signal:
|
|
205
|
+
"""Evaluate layout stability (element movement)."""
|
|
206
|
+
is_shifting = state.get('layout_shifting', False)
|
|
207
|
+
|
|
208
|
+
return Signal(
|
|
209
|
+
signal_type=SignalType.LAYOUT_SHIFT,
|
|
210
|
+
state=SignalState.STABLE if not is_shifting else SignalState.UNSTABLE,
|
|
211
|
+
value=is_shifting,
|
|
212
|
+
threshold=False,
|
|
213
|
+
is_mandatory=self.config.strictness == 'strict',
|
|
214
|
+
details="Layout shifting detected" if is_shifting else "Layout stable",
|
|
215
|
+
)
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waitless
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Eliminate explicit waits in UI automation by detecting true UI stability
|
|
5
|
+
Author-email: Dhiraj Das <dhirajdas.66@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/godhiraj-code/waitless
|
|
8
|
+
Project-URL: Documentation, https://github.com/godhiraj-code/waitless#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/godhiraj-code/waitless.git
|
|
10
|
+
Project-URL: Issues, https://github.com/godhiraj-code/waitless/issues
|
|
11
|
+
Keywords: selenium,automation,testing,ui-testing,wait,stability,flaky-tests
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: selenium>=4.0; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# Waitless
|
|
30
|
+
|
|
31
|
+
**Zero-wait UI automation stabilization for Selenium**
|
|
32
|
+
|
|
33
|
+
Eliminate explicit waits and sleeps by automatically detecting true UI stability.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install waitless
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from selenium import webdriver
|
|
45
|
+
from selenium.webdriver.common.by import By
|
|
46
|
+
from waitless import stabilize
|
|
47
|
+
|
|
48
|
+
# Create driver as usual
|
|
49
|
+
driver = webdriver.Chrome()
|
|
50
|
+
|
|
51
|
+
# Enable automatic stabilization - ONE LINE
|
|
52
|
+
driver = stabilize(driver)
|
|
53
|
+
|
|
54
|
+
# All interactions now auto-wait for stability
|
|
55
|
+
driver.get("https://example.com")
|
|
56
|
+
driver.find_element(By.ID, "login-button").click() # ← Auto-waits!
|
|
57
|
+
driver.find_element(By.ID, "username").send_keys("user") # ← Auto-waits!
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Why Waitless?
|
|
61
|
+
|
|
62
|
+
### The Problem
|
|
63
|
+
|
|
64
|
+
Automation tests fail because interactions happen while the UI is still changing:
|
|
65
|
+
|
|
66
|
+
- DOM mutations from React/Vue/Angular updates
|
|
67
|
+
- In-flight AJAX requests
|
|
68
|
+
- CSS animations and transitions
|
|
69
|
+
- Layout shifts from lazy-loaded content
|
|
70
|
+
|
|
71
|
+
### Traditional Solutions (and why they fail)
|
|
72
|
+
|
|
73
|
+
| Approach | Problem |
|
|
74
|
+
|----------|---------|
|
|
75
|
+
| `time.sleep(2)` | Too slow, still fails sometimes |
|
|
76
|
+
| `WebDriverWait` | Only checks one element, misses page-wide state |
|
|
77
|
+
| Retries | Masks the real problem, adds flakiness |
|
|
78
|
+
|
|
79
|
+
### The Waitless Solution
|
|
80
|
+
|
|
81
|
+
Waitless monitors the **entire page** for stability signals:
|
|
82
|
+
|
|
83
|
+
- ✅ DOM mutation activity (MutationObserver)
|
|
84
|
+
- ✅ Pending network requests (XHR/fetch interception)
|
|
85
|
+
- ✅ CSS animations and transitions
|
|
86
|
+
- ✅ Layout stability (element movement)
|
|
87
|
+
|
|
88
|
+
When you interact, waitless ensures the page is truly ready.
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from waitless import stabilize, StabilizationConfig
|
|
94
|
+
|
|
95
|
+
config = StabilizationConfig(
|
|
96
|
+
timeout=5, # Max wait time (seconds)
|
|
97
|
+
dom_settle_time=0.1, # DOM quiet period needed
|
|
98
|
+
network_idle_threshold=0, # Max pending requests (0 = all must complete)
|
|
99
|
+
animation_detection=True, # Wait for animations to finish
|
|
100
|
+
strictness='normal', # 'strict' | 'normal' | 'relaxed'
|
|
101
|
+
debug_mode=True # Enable logging
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
driver = stabilize(driver, config=config)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Strictness Levels
|
|
108
|
+
|
|
109
|
+
| Level | What It Waits For |
|
|
110
|
+
|-------|-------------------|
|
|
111
|
+
| `strict` | DOM + Network + Animations + Layout |
|
|
112
|
+
| `normal` | DOM + Network (default) |
|
|
113
|
+
| `relaxed` | DOM only |
|
|
114
|
+
|
|
115
|
+
### Factory Methods
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# For strict testing
|
|
119
|
+
config = StabilizationConfig.strict()
|
|
120
|
+
|
|
121
|
+
# For apps with background traffic
|
|
122
|
+
config = StabilizationConfig.relaxed()
|
|
123
|
+
|
|
124
|
+
# For CI environments
|
|
125
|
+
config = StabilizationConfig.ci()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Manual Stabilization
|
|
129
|
+
|
|
130
|
+
If you don't want to wrap the driver:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from waitless import wait_for_stability
|
|
134
|
+
|
|
135
|
+
wait_for_stability(driver)
|
|
136
|
+
driver.find_element(By.ID, "button").click()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Disabling Stabilization
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from waitless import unstabilize
|
|
143
|
+
|
|
144
|
+
driver = unstabilize(driver) # Back to original behavior
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Diagnostics
|
|
148
|
+
|
|
149
|
+
When tests fail, get detailed analysis:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from waitless import get_diagnostics, StabilizationTimeout
|
|
153
|
+
from waitless.diagnostics import print_report
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
driver.find_element(By.ID, "slow-button").click()
|
|
157
|
+
except StabilizationTimeout as e:
|
|
158
|
+
diagnostics = get_diagnostics(driver)
|
|
159
|
+
print_report(engine) # Print detailed report
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### CLI Doctor Command
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
python -m waitless doctor --file diagnostics.json
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Sample output:
|
|
169
|
+
```
|
|
170
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
171
|
+
║ WAITLESS STABILITY REPORT ║
|
|
172
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
173
|
+
║ BLOCKING FACTORS: ║
|
|
174
|
+
║ ⚠ NETWORK: 2 request(s) still pending ║
|
|
175
|
+
║ → GET /api/users ║
|
|
176
|
+
║ ⚠ ANIMATIONS: 1 active animation(s) ║
|
|
177
|
+
╠══════════════════════════════════════════════════════════════════╣
|
|
178
|
+
║ SUGGESTIONS: ║
|
|
179
|
+
║ 1. Set network_idle_threshold=2 for background traffic ║
|
|
180
|
+
║ 2. Use animation_detection=False for infinite spinners ║
|
|
181
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Important Notes
|
|
185
|
+
|
|
186
|
+
### Network Threshold Warning
|
|
187
|
+
|
|
188
|
+
The default `network_idle_threshold=0` means **all** network requests must complete.
|
|
189
|
+
|
|
190
|
+
Many apps have background traffic that never stops:
|
|
191
|
+
- Analytics calls
|
|
192
|
+
- Long polling
|
|
193
|
+
- Feature flags
|
|
194
|
+
- WebSocket heartbeats
|
|
195
|
+
|
|
196
|
+
If tests timeout frequently, try:
|
|
197
|
+
```python
|
|
198
|
+
config = StabilizationConfig(network_idle_threshold=2)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Wrapped Elements
|
|
202
|
+
|
|
203
|
+
The stabilized driver returns wrapped elements that auto-wait. They behave like WebElements but:
|
|
204
|
+
|
|
205
|
+
- `isinstance(element, WebElement)` returns `False`
|
|
206
|
+
- Use `.unwrap()` to get the original element if needed
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
element = driver.find_element(By.ID, "button")
|
|
210
|
+
original = element.unwrap() # Gets the real WebElement
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## v0 Limitations
|
|
214
|
+
|
|
215
|
+
- **Selenium only** - Playwright support planned for v1
|
|
216
|
+
- **Sync only** - No async/await support yet
|
|
217
|
+
- **Main frame only** - iframes not monitored
|
|
218
|
+
- **No Shadow DOM** - MutationObserver doesn't see shadow roots
|
|
219
|
+
- **No Service Workers** - SW network requests not intercepted
|
|
220
|
+
|
|
221
|
+
## API Reference
|
|
222
|
+
|
|
223
|
+
### Functions
|
|
224
|
+
|
|
225
|
+
| Function | Description |
|
|
226
|
+
|----------|-------------|
|
|
227
|
+
| `stabilize(driver, config=None)` | Enable auto-stabilization |
|
|
228
|
+
| `unstabilize(driver)` | Disable and return original driver |
|
|
229
|
+
| `wait_for_stability(driver, timeout=None)` | Manual one-time wait |
|
|
230
|
+
| `get_diagnostics(driver)` | Get diagnostic data |
|
|
231
|
+
|
|
232
|
+
### Classes
|
|
233
|
+
|
|
234
|
+
| Class | Description |
|
|
235
|
+
|-------|-------------|
|
|
236
|
+
| `StabilizationConfig` | Configuration options |
|
|
237
|
+
| `StabilizedWebDriver` | Wrapped driver with auto-wait |
|
|
238
|
+
| `StabilizedWebElement` | Wrapped element with auto-wait |
|
|
239
|
+
| `StabilizationTimeout` | Exception when UI doesn't stabilize |
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
waitless/__init__.py,sha256=R3RDZb6qVdCWPmhVbCXNU_VFHHuinV7YlB2pdK6SU1A,2244
|
|
2
|
+
waitless/__main__.py,sha256=hqJRbJOqOhGJIMBOL6vsvRFmk8KsAofuFAOg65P4KJo,3915
|
|
3
|
+
waitless/config.py,sha256=AJEUUXZwKvTkCl-jIjLZHfk93EE2sHeXn_bY7mN7HeA,6068
|
|
4
|
+
waitless/diagnostics.py,sha256=BmPhaXMhJpWh2fR7fPzM8f6_wnqu4Uxurq1_IhzMaRM,7709
|
|
5
|
+
waitless/engine.py,sha256=fvUY-64rQjPVabDL5oGj4MUpuBFF8j9m9Kbvzohc8yA,9854
|
|
6
|
+
waitless/exceptions.py,sha256=xSsOjNgpSrx7hXqg1xKFAq6quIa8VWx81YyZ5GOUllQ,3578
|
|
7
|
+
waitless/instrumentation.py,sha256=6XF4_xJnTx3mKNR5ySTeI7s6iIcq9t9qvuRl8SwrLa8,11606
|
|
8
|
+
waitless/selenium_integration.py,sha256=mUS_wRowkXQZuvMS0auHZ1MzTuWoqCU6wn5jOcJM-M0,10061
|
|
9
|
+
waitless/signals.py,sha256=lCgSNHhm1309gy2blQdjK8hTyZvXSYuFi9H0P6x0ngs,7537
|
|
10
|
+
waitless-0.1.0.dist-info/METADATA,sha256=o6TzoJfC_WFjWSynpB9yfTncylFMzNeEKjA8DMRn064,7925
|
|
11
|
+
waitless-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
waitless-0.1.0.dist-info/entry_points.txt,sha256=xbJnCccN-M60RRIAaLOV8ILP6C6xwt18ykmfYJXUShU,52
|
|
13
|
+
waitless-0.1.0.dist-info/top_level.txt,sha256=tRSHQHQXgHldCfnMRAjwrjd4ezBGe4dJUCOD5ABtPJc,9
|
|
14
|
+
waitless-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
waitless
|