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 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