watir-dom-wait 0.1.3 → 0.2.0
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.
- data/README.md +2 -2
- data/lib/watir/dom/elements/element.rb +9 -18
- data/lib/watir/dom/extensions/js/waitForDom.js +38 -42
- data/lib/watir/dom/wait.rb +29 -3
- data/lib/watir/dom/wait/version.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/support/html/wait_for_dom.html +29 -11
- data/spec/watir-dom-wait/element_spec.rb +16 -3
- metadata +4 -4
data/README.md
CHANGED
@@ -78,9 +78,9 @@ Watir::Dom::Wait.interval = 0.15
|
|
78
78
|
|
79
79
|
## How it works
|
80
80
|
|
81
|
-
|
81
|
+
Using [MutationObserver](https://developer.mozilla.org/en/docs/Web/API/MutationObserver).
|
82
82
|
|
83
|
-
Note, that it also rescues `Selenium::WebDriver::Error::StaleElementReferenceError
|
83
|
+
Note, that it also rescues `Selenium::WebDriver::Error::StaleElementReferenceError`, `Selenium::WebDriver::Error::JavascriptError` and `Watir::Exception::UnknownObjectException` (only when its message contains `Element not found in the cache - perhaps the page has changed since it was looked up`) when waits for DOM.
|
84
84
|
|
85
85
|
## Contributors
|
86
86
|
|
@@ -33,25 +33,16 @@ module Watir
|
|
33
33
|
opts[:interval] ||= Dom::Wait.interval
|
34
34
|
opts[:delay] ||= Dom::Wait.delay
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
36
|
+
Dom::Wait.rescue do
|
37
|
+
if block_given?
|
38
|
+
js = Dom::Wait::JAVASCRIPT.dup
|
39
|
+
browser.execute_script js, self, opts[:interval], opts[:delay]
|
40
|
+
Wait.until(opts[:timeout], message) { browser.execute_script(Dom::Wait::DOM_READY) == 0 }
|
41
|
+
yield self
|
42
|
+
else
|
43
|
+
WhenDOMChangedDecorator.new(self, opts, message)
|
44
|
+
end
|
43
45
|
end
|
44
|
-
|
45
|
-
rescue Selenium::WebDriver::Error::StaleElementReferenceError, Selenium::WebDriver::Error::JavascriptError
|
46
|
-
# StaleElementReferenceError
|
47
|
-
# element can become stale, so we just retry DOM waiting
|
48
|
-
#
|
49
|
-
# JavascriptError
|
50
|
-
# in rare cases, args passed to execute script are not correct, for example:
|
51
|
-
# correct: [#<Watir::Body:0x6bb2ccb9de06cb92 located=false selector={:element=>(webdriver element)}>, 300, 3000] [el, interval, delay]
|
52
|
-
# incorrect: [0.3, 3000, nil] [interval, delay, ?]
|
53
|
-
# TODO there might be some logic (bug?) in Selenium which does this
|
54
|
-
retry
|
55
46
|
end
|
56
47
|
|
57
48
|
#
|
@@ -56,27 +56,30 @@ function Watir(options) {
|
|
56
56
|
this.set(options);
|
57
57
|
|
58
58
|
this.startedModifying = false;
|
59
|
-
this.domReady
|
59
|
+
this.domReady = 1;
|
60
|
+
this.observers = {
|
61
|
+
forceReady: null,
|
62
|
+
startModifying: null
|
63
|
+
}
|
60
64
|
|
61
|
-
this.
|
65
|
+
this.addObservers();
|
62
66
|
this.exitOnTimeout();
|
63
67
|
};
|
64
68
|
|
65
|
-
Watir.prototype.set = function
|
66
|
-
if (options == null) {
|
69
|
+
Watir.prototype.set = function(options) {
|
70
|
+
if (options == null) {
|
67
71
|
options = {};
|
68
72
|
}
|
69
73
|
|
70
74
|
this.el = options.el;
|
71
|
-
this.event = options.event || 'DOMSubtreeModified';
|
72
75
|
this.interval = options.interval;
|
73
76
|
this.delay = options.delay;
|
74
77
|
|
75
|
-
this.forceReady
|
78
|
+
this.forceReady = this.wrappedForceReady();
|
76
79
|
this.startModifying = this.wrappedStartModifying();
|
77
80
|
}
|
78
81
|
|
79
|
-
Watir.prototype.exitOnTimeout = function
|
82
|
+
Watir.prototype.exitOnTimeout = function() {
|
80
83
|
var that = this;
|
81
84
|
|
82
85
|
Underscore.delay(function() {
|
@@ -86,29 +89,47 @@ Watir.prototype.exitOnTimeout = function () {
|
|
86
89
|
}, this.delay);
|
87
90
|
};
|
88
91
|
|
89
|
-
Watir.prototype.
|
90
|
-
this.
|
91
|
-
this.
|
92
|
+
Watir.prototype.addObservers = function() {
|
93
|
+
this.observers.forceReady = this.observe(this.el, this.forceReady);
|
94
|
+
this.observers.startModifying = this.observe(this.el, this.startModifying);
|
92
95
|
}
|
93
96
|
|
94
|
-
Watir.prototype.
|
95
|
-
this.
|
96
|
-
this.
|
97
|
+
Watir.prototype.removeObservers = function() {
|
98
|
+
this.unobserve(this.observers.forceReady);
|
99
|
+
this.unobserve(this.observers.startModifying);
|
97
100
|
}
|
98
101
|
|
99
|
-
Watir.prototype.
|
102
|
+
Watir.prototype.observe = function(el, fn) {
|
103
|
+
var observer = new MutationObserver(function(mutations) {
|
104
|
+
fn();
|
105
|
+
});
|
106
|
+
var config = {
|
107
|
+
attributes: true,
|
108
|
+
childList: true,
|
109
|
+
characterData: true,
|
110
|
+
subtree: true
|
111
|
+
};
|
112
|
+
observer.observe(el, config);
|
113
|
+
return observer;
|
114
|
+
};
|
115
|
+
|
116
|
+
Watir.prototype.unobserve = function(observer) {
|
117
|
+
observer.disconnect();
|
118
|
+
};
|
119
|
+
|
120
|
+
Watir.prototype.wrappedForceReady = function() {
|
100
121
|
return Underscore.bind(Underscore.debounce(this._forceReady, this.interval), this);
|
101
122
|
};
|
102
123
|
|
103
|
-
Watir.prototype.wrappedStartModifying = function
|
124
|
+
Watir.prototype.wrappedStartModifying = function() {
|
104
125
|
return Underscore.bind(Underscore.once(this._startModifying, this.interval), this);
|
105
126
|
}
|
106
127
|
|
107
|
-
Watir.prototype._forceReady = function() {
|
128
|
+
Watir.prototype._forceReady = function(mutations) {
|
108
129
|
var that = this;
|
109
130
|
|
110
131
|
Underscore.once(function() {
|
111
|
-
that.
|
132
|
+
that.removeObservers();
|
112
133
|
that.domReady -= 1;
|
113
134
|
})();
|
114
135
|
}
|
@@ -117,33 +138,8 @@ Watir.prototype._startModifying = function() {
|
|
117
138
|
this.startedModifying = true;
|
118
139
|
}
|
119
140
|
|
120
|
-
Watir.prototype.addEventListener = (function() {
|
121
|
-
if (window.addEventListener) {
|
122
|
-
return function (fn) {
|
123
|
-
this.el.addEventListener(this.event, fn, false);
|
124
|
-
};
|
125
|
-
} else {
|
126
|
-
return function (fn) {
|
127
|
-
this.el.attachEvent('on' + this.event, fn)
|
128
|
-
};
|
129
|
-
}
|
130
|
-
})();
|
131
|
-
|
132
|
-
Watir.prototype.removeEventListener = (function() {
|
133
|
-
if (window.removeEventListener) {
|
134
|
-
return function (fn) {
|
135
|
-
this.el.removeEventListener(this.event, fn, false);
|
136
|
-
};
|
137
|
-
} else {
|
138
|
-
return function (fn) {
|
139
|
-
this.el.detachEvent('on' + this.event, fn)
|
140
|
-
};
|
141
|
-
}
|
142
|
-
})();
|
143
|
-
|
144
141
|
window.watir = new Watir({
|
145
142
|
el: arguments[0],
|
146
143
|
interval: arguments[1] * 1000,
|
147
144
|
delay: arguments[2] * 1000,
|
148
145
|
});
|
149
|
-
|
data/lib/watir/dom/wait.rb
CHANGED
@@ -28,6 +28,30 @@ module Watir
|
|
28
28
|
@delay ||= 1
|
29
29
|
end
|
30
30
|
|
31
|
+
#
|
32
|
+
# Executes block rescuing all necessary exceptions.
|
33
|
+
# @param [Proc] block
|
34
|
+
# @api private
|
35
|
+
#
|
36
|
+
|
37
|
+
def rescue(&block)
|
38
|
+
block.call
|
39
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError, Exception::UnknownObjectException => error
|
40
|
+
msg = 'Element not found in the cache - perhaps the page has changed since it was looked up'
|
41
|
+
if error.is_a?(Watir::Exception::UnknownObjectException) && !error.message.include?(msg)
|
42
|
+
raise error
|
43
|
+
else
|
44
|
+
# element can become stale, so we just retry DOM waiting
|
45
|
+
retry
|
46
|
+
end
|
47
|
+
rescue Selenium::WebDriver::Error::JavascriptError
|
48
|
+
# in rare cases, args passed to execute script are not correct, for example:
|
49
|
+
# correct: [#<Watir::Body:0x6bb2ccb9de06cb92 located=false selector={:element=>(webdriver element)}>, 300, 3000] [el, interval, delay]
|
50
|
+
# incorrect: [0.3, 3000, nil] [interval, delay, ?]
|
51
|
+
# TODO there might be some logic (bug?) in Selenium which does this
|
52
|
+
retry
|
53
|
+
end
|
54
|
+
|
31
55
|
end # << self
|
32
56
|
end # Wait
|
33
57
|
end # Dom
|
@@ -52,10 +76,12 @@ module Watir
|
|
52
76
|
raise NoMethodError, "undefined method `#{m}' for #{@element.inspect}:#{@element.class}"
|
53
77
|
end
|
54
78
|
|
55
|
-
|
56
|
-
|
79
|
+
Dom::Wait.rescue do
|
80
|
+
@element.browser.execute_script @js, @element, @opts[:interval], @opts[:delay]
|
81
|
+
Watir::Wait.until(@opts[:timeout], @message) { @element.browser.execute_script(Dom::Wait::DOM_READY) == 0 }
|
57
82
|
|
58
|
-
|
83
|
+
@element.__send__(m, *args, &block)
|
84
|
+
end
|
59
85
|
end
|
60
86
|
|
61
87
|
def respond_to?(*args)
|
data/spec/spec_helper.rb
CHANGED
@@ -17,30 +17,48 @@
|
|
17
17
|
}, number * timeout);
|
18
18
|
}
|
19
19
|
|
20
|
-
|
20
|
+
function staleDiv(parent_id, child_id) {
|
21
21
|
var parent = document.getElementById(parent_id);
|
22
22
|
var child = document.getElementById(child_id);
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
var new_div = document.createElement("div");
|
24
|
+
new_div.id = child_id;
|
25
|
+
new_div.style = 'display: block;';
|
26
26
|
|
27
27
|
setTimeout(function() {
|
28
|
-
|
29
|
-
|
28
|
+
parent.removeChild(child);
|
29
|
+
parent.appendChild(new_div);
|
30
30
|
}, 1000);
|
31
|
-
|
31
|
+
}
|
32
|
+
|
33
|
+
function fadeIn(id, value) {
|
34
|
+
var el = document.getElementById(id);
|
35
|
+
el.style.opacity = '0.' + value;
|
36
|
+
if (value < 9) {
|
37
|
+
console.log(value);
|
38
|
+
value++;
|
39
|
+
setTimeout(function() { fadeIn(id, value) }, 400);
|
40
|
+
} else {
|
41
|
+
el.innerHTML = 'Faded';
|
42
|
+
return;
|
43
|
+
}
|
44
|
+
}
|
32
45
|
</script>
|
33
46
|
</head>
|
34
47
|
|
35
48
|
<body>
|
36
|
-
<div id="container1">
|
37
|
-
<div id="container2"></div>
|
38
|
-
</div>
|
39
|
-
<br />
|
40
49
|
<button onclick="modifySubtree('container1', 5, 1000);" id="long">Start modifying subtree with 5 node operations and 1000 ms delay between them</button>
|
41
50
|
<br />
|
42
51
|
<button onclick="modifySubtree('container1', 20, 100);" id="quick">Start modifying subtree with 20 node operations and 100 ms delay between them</button>
|
43
52
|
<br />
|
44
53
|
<button onclick="modifySubtree('container2', 20, 100); staleDiv('container1', 'container2');" id="stale">Start modifying and then update child div</button>
|
54
|
+
<br />
|
55
|
+
<button onclick="fadeIn('span', 0)" id="fade">Start fading in text</button>
|
56
|
+
<br />
|
57
|
+
<div id="container1">
|
58
|
+
<div id="container2"></div>
|
59
|
+
</div>
|
60
|
+
<div id="container3">
|
61
|
+
<span id="span" style="opacity: 0">To Fade</span>
|
62
|
+
</div>
|
45
63
|
</body>
|
46
64
|
</html>
|
@@ -65,6 +65,14 @@ describe Watir::Element do
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
context "when effects are used" do
|
69
|
+
it "properly handles fading" do
|
70
|
+
@browser.button(:id => 'fade').click
|
71
|
+
text = @browser.div(:id => 'container3').when_dom_changed.span.text
|
72
|
+
expect(text).to eq('Faded')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
68
76
|
context "when element goes stale" do
|
69
77
|
before(:all) do
|
70
78
|
Watir.always_locate = false
|
@@ -78,9 +86,14 @@ describe Watir::Element do
|
|
78
86
|
div = @browser.div(:id => 'container2')
|
79
87
|
div.exists?
|
80
88
|
@browser.refresh
|
81
|
-
expect {
|
82
|
-
|
83
|
-
|
89
|
+
expect { div.when_dom_changed.text }.not_to raise_error
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "when element cannot be located" do
|
94
|
+
it "relocates element" do
|
95
|
+
div = @browser.div(:id => 'doesnotexist')
|
96
|
+
expect { div.when_dom_changed.text }.to raise_error(Watir::Exception::UnknownObjectException)
|
84
97
|
end
|
85
98
|
end
|
86
99
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: watir-dom-wait
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: watir-webdriver
|
@@ -112,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
112
|
version: '0'
|
113
113
|
segments:
|
114
114
|
- 0
|
115
|
-
hash: -
|
115
|
+
hash: -2698553052236191813
|
116
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
121
|
version: '0'
|
122
122
|
segments:
|
123
123
|
- 0
|
124
|
-
hash: -
|
124
|
+
hash: -2698553052236191813
|
125
125
|
requirements: []
|
126
126
|
rubyforge_project:
|
127
127
|
rubygems_version: 1.8.23
|