waitless 0.3.0__tar.gz → 0.3.1__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-0.3.0/waitless.egg-info → waitless-0.3.1}/PKG-INFO +2 -3
- {waitless-0.3.0 → waitless-0.3.1}/README.md +1 -2
- {waitless-0.3.0 → waitless-0.3.1}/pyproject.toml +1 -1
- {waitless-0.3.0 → waitless-0.3.1}/waitless/__init__.py +1 -1
- {waitless-0.3.0 → waitless-0.3.1}/waitless/instrumentation.py +83 -4
- {waitless-0.3.0 → waitless-0.3.1}/waitless/signals.py +14 -2
- {waitless-0.3.0 → waitless-0.3.1/waitless.egg-info}/PKG-INFO +2 -3
- {waitless-0.3.0 → waitless-0.3.1}/LICENSE +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/setup.cfg +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless/__main__.py +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless/config.py +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless/diagnostics.py +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless/engine.py +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless/exceptions.py +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless/selenium_integration.py +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless.egg-info/SOURCES.txt +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless.egg-info/dependency_links.txt +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless.egg-info/entry_points.txt +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless.egg-info/requires.txt +0 -0
- {waitless-0.3.0 → waitless-0.3.1}/waitless.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: waitless
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -84,7 +84,7 @@ Automation tests fail because interactions happen while the UI is still changing
|
|
|
84
84
|
|
|
85
85
|
Waitless monitors the **entire page** for stability signals:
|
|
86
86
|
|
|
87
|
-
- ✅ DOM mutation activity (MutationObserver)
|
|
87
|
+
- ✅ DOM mutation activity (MutationObserver, including **Shadow DOM**)
|
|
88
88
|
- ✅ Pending network requests (XHR/fetch interception)
|
|
89
89
|
- ✅ CSS animations and transitions
|
|
90
90
|
- ✅ Layout stability (element movement)
|
|
@@ -219,7 +219,6 @@ original = element.unwrap() # Gets the real WebElement
|
|
|
219
219
|
- **Selenium only** - Playwright support planned for v1
|
|
220
220
|
- **Sync only** - No async/await support yet
|
|
221
221
|
- **Main frame only** - iframes not monitored
|
|
222
|
-
- **No Shadow DOM** - MutationObserver doesn't see shadow roots
|
|
223
222
|
- **No Service Workers** - SW network requests not intercepted
|
|
224
223
|
|
|
225
224
|
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
@@ -54,7 +54,7 @@ Automation tests fail because interactions happen while the UI is still changing
|
|
|
54
54
|
|
|
55
55
|
Waitless monitors the **entire page** for stability signals:
|
|
56
56
|
|
|
57
|
-
- ✅ DOM mutation activity (MutationObserver)
|
|
57
|
+
- ✅ DOM mutation activity (MutationObserver, including **Shadow DOM**)
|
|
58
58
|
- ✅ Pending network requests (XHR/fetch interception)
|
|
59
59
|
- ✅ CSS animations and transitions
|
|
60
60
|
- ✅ Layout stability (element movement)
|
|
@@ -189,7 +189,6 @@ original = element.unwrap() # Gets the real WebElement
|
|
|
189
189
|
- **Selenium only** - Playwright support planned for v1
|
|
190
190
|
- **Sync only** - No async/await support yet
|
|
191
191
|
- **Main frame only** - iframes not monitored
|
|
192
|
-
- **No Shadow DOM** - MutationObserver doesn't see shadow roots
|
|
193
192
|
- **No Service Workers** - SW network requests not intercepted
|
|
194
193
|
|
|
195
194
|
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
@@ -71,6 +71,7 @@ INSTRUMENTATION_SCRIPT = """
|
|
|
71
71
|
// Rolling window for mutation rate calculation
|
|
72
72
|
_mutationTimestamps: [],
|
|
73
73
|
_mutationWindowMs: 1000, // 1 second window for rate calculation
|
|
74
|
+
_observedShadowRoots: new WeakSet(),
|
|
74
75
|
|
|
75
76
|
_setupMutationObserver: function() {
|
|
76
77
|
var self = this;
|
|
@@ -87,17 +88,77 @@ INSTRUMENTATION_SCRIPT = """
|
|
|
87
88
|
self._mutationTimestamps.shift();
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
// Check for new shadow roots in added nodes
|
|
92
|
+
mutations.forEach(function(mutation) {
|
|
93
|
+
mutation.addedNodes.forEach(function(node) {
|
|
94
|
+
if (node.nodeType === 1) { // Element node
|
|
95
|
+
self._observeShadowRoots(node);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
90
100
|
self._log('DOM mutation', { count: mutations.length, rate: self.getMutationRate() });
|
|
91
101
|
});
|
|
92
102
|
|
|
93
|
-
|
|
103
|
+
var config = {
|
|
94
104
|
childList: true,
|
|
95
105
|
subtree: true,
|
|
96
106
|
attributes: true,
|
|
97
107
|
characterData: true
|
|
98
|
-
}
|
|
108
|
+
};
|
|
99
109
|
|
|
110
|
+
observer.observe(document.documentElement || document.body, config);
|
|
100
111
|
this._observers.push(observer);
|
|
112
|
+
|
|
113
|
+
// Initial scan for shadow roots
|
|
114
|
+
this._observeShadowRoots(document);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
_observeShadowRoots: function(root) {
|
|
118
|
+
var self = this;
|
|
119
|
+
|
|
120
|
+
// Function to recursively find and observe shadow roots
|
|
121
|
+
var walk = function(node) {
|
|
122
|
+
if (node.shadowRoot && !self._observedShadowRoots.has(node.shadowRoot)) {
|
|
123
|
+
self._observedShadowRoots.add(node.shadowRoot);
|
|
124
|
+
|
|
125
|
+
var observer = new MutationObserver(function(mutations) {
|
|
126
|
+
var now = Date.now();
|
|
127
|
+
self.lastMutationTime = now;
|
|
128
|
+
self._mutationTimestamps.push(now);
|
|
129
|
+
self._log('Shadow DOM mutation', { count: mutations.length });
|
|
130
|
+
|
|
131
|
+
// Scan new nodes in shadow DOM for nested shadow roots
|
|
132
|
+
mutations.forEach(function(mutation) {
|
|
133
|
+
mutation.addedNodes.forEach(function(newNode) {
|
|
134
|
+
if (newNode.nodeType === 1) walk(newNode);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
observer.observe(node.shadowRoot, {
|
|
140
|
+
childList: true,
|
|
141
|
+
subtree: true,
|
|
142
|
+
attributes: true,
|
|
143
|
+
characterData: true
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
self._observers.push(observer);
|
|
147
|
+
self._log('Observing shadow root', { host: node.tagName });
|
|
148
|
+
|
|
149
|
+
// Recurse into the shadow root
|
|
150
|
+
walk(node.shadowRoot);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Traverse children
|
|
154
|
+
var child = node.firstElementChild;
|
|
155
|
+
while (child) {
|
|
156
|
+
walk(child);
|
|
157
|
+
child = child.nextElementSibling;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
walk(root);
|
|
101
162
|
},
|
|
102
163
|
|
|
103
164
|
// Calculate mutations per second from rolling window
|
|
@@ -239,8 +300,26 @@ INSTRUMENTATION_SCRIPT = """
|
|
|
239
300
|
},
|
|
240
301
|
|
|
241
302
|
_checkLayoutStability: function() {
|
|
242
|
-
// Track key interactive elements
|
|
243
|
-
var elements =
|
|
303
|
+
// Track key interactive elements, including those in shadow DOM
|
|
304
|
+
var elements = [];
|
|
305
|
+
|
|
306
|
+
var collectElements = function(root) {
|
|
307
|
+
var found = root.querySelectorAll('button, a, input, [onclick], [role="button"]');
|
|
308
|
+
for (var i = 0; i < found.length; i++) {
|
|
309
|
+
elements.push(found[i]);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Recursively check shadow roots
|
|
313
|
+
var all = root.querySelectorAll('*');
|
|
314
|
+
for (var j = 0; j < all.length; j++) {
|
|
315
|
+
if (all[j].shadowRoot) {
|
|
316
|
+
collectElements(all[j].shadowRoot);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
collectElements(document);
|
|
322
|
+
|
|
244
323
|
var isShifting = false;
|
|
245
324
|
var self = this;
|
|
246
325
|
|
|
@@ -169,14 +169,26 @@ 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
|
-
|
|
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
|
|
180
|
+
|
|
181
|
+
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
|
+
|
|
173
185
|
return Signal(
|
|
174
186
|
signal_type=SignalType.DOM_MUTATIONS,
|
|
175
187
|
state=SignalState.STABLE if is_stable else SignalState.UNSTABLE,
|
|
176
188
|
value=mutation_rate,
|
|
177
189
|
threshold=threshold,
|
|
178
190
|
is_mandatory=True,
|
|
179
|
-
details=
|
|
191
|
+
details=details,
|
|
180
192
|
)
|
|
181
193
|
|
|
182
194
|
# Fallback: Use time since last mutation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: waitless
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -84,7 +84,7 @@ Automation tests fail because interactions happen while the UI is still changing
|
|
|
84
84
|
|
|
85
85
|
Waitless monitors the **entire page** for stability signals:
|
|
86
86
|
|
|
87
|
-
- ✅ DOM mutation activity (MutationObserver)
|
|
87
|
+
- ✅ DOM mutation activity (MutationObserver, including **Shadow DOM**)
|
|
88
88
|
- ✅ Pending network requests (XHR/fetch interception)
|
|
89
89
|
- ✅ CSS animations and transitions
|
|
90
90
|
- ✅ Layout stability (element movement)
|
|
@@ -219,7 +219,6 @@ original = element.unwrap() # Gets the real WebElement
|
|
|
219
219
|
- **Selenium only** - Playwright support planned for v1
|
|
220
220
|
- **Sync only** - No async/await support yet
|
|
221
221
|
- **Main frame only** - iframes not monitored
|
|
222
|
-
- **No Shadow DOM** - MutationObserver doesn't see shadow roots
|
|
223
222
|
- **No Service Workers** - SW network requests not intercepted
|
|
224
223
|
|
|
225
224
|
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
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
|