watir-dom-wait 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -78,9 +78,9 @@ Watir::Dom::Wait.interval = 0.15
78
78
 
79
79
  ## How it works
80
80
 
81
- By attaching `DOMSubtreeModified` event to element. It's supported in all major browsers (except Presto-powered Opera).
81
+ Using [MutationObserver](https://developer.mozilla.org/en/docs/Web/API/MutationObserver).
82
82
 
83
- Note, that it also rescues `Selenium::WebDriver::Error::StaleElementReferenceError` and `Selenium::WebDriver::Error::JavascriptError` when waits for DOM.
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
- if block_given?
37
- js = Dom::Wait::JAVASCRIPT.dup
38
- browser.execute_script js, self, opts[:interval], opts[:delay]
39
- Wait.until(opts[:timeout], message) { browser.execute_script(Dom::Wait::DOM_READY) == 0 }
40
- yield self
41
- else
42
- WhenDOMChangedDecorator.new(self, opts, message)
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 = 1;
59
+ this.domReady = 1;
60
+ this.observers = {
61
+ forceReady: null,
62
+ startModifying: null
63
+ }
60
64
 
61
- this.bindDOMEvents();
65
+ this.addObservers();
62
66
  this.exitOnTimeout();
63
67
  };
64
68
 
65
- Watir.prototype.set = function (options) {
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 = this.wrappedForceReady();
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.bindDOMEvents = function() {
90
- this.addEventListener(this.forceReady);
91
- this.addEventListener(this.startModifying);
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.unbindDOMEvents = function() {
95
- this.removeEventListener(this.forceReady);
96
- this.removeEventListener(this.startModifying);
97
+ Watir.prototype.removeObservers = function() {
98
+ this.unobserve(this.observers.forceReady);
99
+ this.unobserve(this.observers.startModifying);
97
100
  }
98
101
 
99
- Watir.prototype.wrappedForceReady = function () {
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.unbindDOMEvents();
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
-
@@ -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
- @element.browser.execute_script @js, @element, @opts[:interval], @opts[:delay]
56
- Watir::Wait.until(@opts[:timeout], @message) { @element.browser.execute_script(Dom::Wait::DOM_READY) == 0 }
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
- @element.__send__(m, *args, &block)
83
+ @element.__send__(m, *args, &block)
84
+ end
59
85
  end
60
86
 
61
87
  def respond_to?(*args)
@@ -1,7 +1,7 @@
1
1
  module Watir
2
2
  module Dom
3
3
  module Wait
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.0"
5
5
  end # Wait
6
6
  end # Dom
7
7
  end # Watir
@@ -1,6 +1,8 @@
1
1
  require "watir/dom/wait"
2
2
 
3
3
  RSpec.configure do |spec|
4
+ spec.filter_run_excluding bug: /\d+/
5
+
4
6
  spec.before(:all) do
5
7
  @browser = Watir::Browser.new
6
8
  @browser.goto "data:text/html,#{File.read('spec/support/html/wait_for_dom.html')}"
@@ -17,30 +17,48 @@
17
17
  }, number * timeout);
18
18
  }
19
19
 
20
- function staleDiv(parent_id, child_id) {
20
+ function staleDiv(parent_id, child_id) {
21
21
  var parent = document.getElementById(parent_id);
22
22
  var child = document.getElementById(child_id);
23
- var new_div = document.createElement("div");
24
- new_div.id = child_id;
25
- new_div.style = 'display: block;';
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
- parent.removeChild(child);
29
- parent.appendChild(new_div);
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
- div.when_dom_changed.text
83
- }.not_to raise_error(Selenium::WebDriver::Error::StaleElementReferenceError)
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.1.3
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-09-05 00:00:00.000000000 Z
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: -357748089059915628
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: -357748089059915628
124
+ hash: -2698553052236191813
125
125
  requirements: []
126
126
  rubyforge_project:
127
127
  rubygems_version: 1.8.23