watir-dom-wait 0.2.0 → 0.2.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ec015b0da5094d2d636dc167932acb2639d65554
4
+ data.tar.gz: 9fed0593eec77385cecc7331fdcc14b33d22dc7b
5
+ SHA512:
6
+ metadata.gz: 2d1289a416ecab865fd4d6c237e74dc9a8aed91641529b326ad02a6ba4345475d6ae7c85ad3f7bedaea5a102a480131bdd3c22e69cc95844104cc1d9dc5e5771
7
+ data.tar.gz: b716cb205b6efb46839ca38b6cf1aea3ff9f9c5c4af4570dcbe7f909de16908f4a3105e9c6d24700e3e039e30b7c49576dcca460019dd0f2dc5d7fd9ef2f6815
@@ -0,0 +1,28 @@
1
+ module Watir
2
+ #
3
+ # Wraps an Element so that any subsequent method calls are
4
+ # put on hold until the DOM subtree is modified within the element.
5
+ #
6
+ class WhenDOMChangedDecorator
7
+
8
+ def initialize(element, opts, message)
9
+ @element = element
10
+ @opts = opts
11
+ @message = message
12
+ end
13
+
14
+ def method_missing(m, *args, &block)
15
+ unless @element.respond_to?(m)
16
+ raise NoMethodError, "undefined method `#{m}' for #{@element.inspect}:#{@element.class}"
17
+ end
18
+
19
+ Dom::Wait.wait_for_dom(@element, @opts, @message)
20
+ @element.__send__(m, *args, &block)
21
+ end
22
+
23
+ def respond_to?(*args)
24
+ @element.respond_to?(*args)
25
+ end
26
+
27
+ end # WhenDOMChangedDecorator
28
+ end # Watir
@@ -22,26 +22,22 @@ module Watir
22
22
  # browser.div(id: 'test').when_dom_changed(delay: 5).a(id: 'link').click
23
23
  #
24
24
  # @param [Hash] opts
25
- # @option opts [Fixnum] timeout seconds to wait before timing out
26
25
  # @option opts [Float] interval How long to wait between DOM nodes adding/removing in seconds. Defaults to 0.5
27
- # @option opts [Float] delay How long to wait for DOM modifications to start in seconds. Defaults to 1
26
+ # @option opts [Float] delay How long to wait for DOM modifications to start
27
+ # @option opts [Fixnum] timeout seconds to wait before timing out
28
28
  #
29
29
 
30
30
  def when_dom_changed(opts = {})
31
31
  message = "waiting for DOM subtree to finish modifying in #{selector_string}"
32
- opts[:timeout] ||= Dom::Wait.timeout
33
32
  opts[:interval] ||= Dom::Wait.interval
34
33
  opts[:delay] ||= Dom::Wait.delay
34
+ opts[:timeout] ||= Dom::Wait.timeout
35
35
 
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
36
+ if block_given?
37
+ Dom::Wait.wait_for_dom(self, opts, message)
38
+ yield self
39
+ else
40
+ WhenDOMChangedDecorator.new(self, opts, message)
45
41
  end
46
42
  end
47
43
 
@@ -49,9 +45,9 @@ module Watir
49
45
  # Waits until DOM is changed within the element.
50
46
  #
51
47
  # @param [Hash] opts
52
- # @option opts [Fixnum] timeout seconds to wait before timing out
53
48
  # @option opts [Float] interval How long to wait between DOM nodes adding/removing in seconds. Defaults to 0.5
54
- # @option opts [Float] delay How long to wait for DOM modifications to start in seconds. Defaults to 1
49
+ # @option opts [Float] delay How long to wait for DOM modifications to start
50
+ # @option opts [Fixnum] timeout seconds to wait before timing out
55
51
  #
56
52
 
57
53
  def wait_until_dom_changed(opts = {})
@@ -1,145 +1,92 @@
1
+ // arguments from WebDriver
2
+ var element = arguments[0];
3
+ var interval = arguments[1] * 1000;
4
+ var delay = arguments[2] * 1000;
5
+ var timeout = arguments[3] * 1000;
6
+ var exit = arguments[4];
7
+
8
+ // flag that DOM has started modifying
9
+ var startedModifying = false;
10
+
11
+ // exits codes
12
+ var exits = {
13
+ modified: 0, // DOM modifications have started and successfully finished
14
+ timeout: 1, // DOM modifications have started but exceeded timeout
15
+ noop: 2, // DOM modifications have not started
16
+ }
17
+
1
18
  /**
2
- * There is a number of required functions provided by Underscore.js,
3
- * but since we don't want to depend on it, we just copy-paste required
4
- * things.
5
- *
6
- * See http://underscorejs.org/docs/underscore.html for explanation of functions.
19
+ * Copy-paste of Underscore.js debounce function.
20
+ * @see http://underscorejs.org/docs/underscore.html#section-67
7
21
  */
8
-
9
- var Underscore = {
10
- slice: Array.prototype.slice,
11
-
12
- nativeBind: Function.prototype.bind,
13
-
14
- once: function(func) {
15
- var ran = false, memo;
16
- return function() {
17
- if (ran) return memo;
18
- ran = true;
19
- memo = func.apply(this, arguments);
20
- func = null;
21
- return memo;
22
- };
23
- },
24
-
25
- delay: function(func, wait) {
26
- var args = this.slice.call(arguments, 2);
27
- return setTimeout(function(){ return func.apply(null, args); }, wait);
28
- },
29
-
30
- debounce: function(func, wait, immediate) {
31
- var timeout, result;
32
- return function() {
33
- var context = this, args = arguments;
34
- var later = function() {
22
+ var _debounce = function(func, wait, immediate) {
23
+ var timeout, args, context, timestamp, result;
24
+ return function() {
25
+ context = this;
26
+ args = arguments;
27
+ timestamp = new Date();
28
+ var later = function() {
29
+ var last = (new Date()) - timestamp;
30
+ if (last < wait) {
31
+ timeout = setTimeout(later, wait - last);
32
+ } else {
35
33
  timeout = null;
36
34
  if (!immediate) result = func.apply(context, args);
37
- };
38
- var callNow = immediate && !timeout;
39
- clearTimeout(timeout);
40
- timeout = setTimeout(later, wait);
41
- if (callNow) result = func.apply(context, args);
42
- return result;
43
- };
44
- },
45
-
46
- bind: function(func, context) {
47
- if (func.bind === this.nativeBind && this.nativeBind) return this.nativeBind.apply(func, this.slice.call(arguments, 1));
48
- var args = this.slice.call(arguments, 2);
49
- return function() {
50
- return func.apply(context, args.concat(this.slice.call(arguments)));
35
+ }
51
36
  };
52
- }
53
- };
54
-
55
- function Watir(options) {
56
- this.set(options);
57
-
58
- this.startedModifying = false;
59
- this.domReady = 1;
60
- this.observers = {
61
- forceReady: null,
62
- startModifying: null
63
- }
64
-
65
- this.addObservers();
66
- this.exitOnTimeout();
67
- };
68
-
69
- Watir.prototype.set = function(options) {
70
- if (options == null) {
71
- options = {};
72
- }
73
-
74
- this.el = options.el;
75
- this.interval = options.interval;
76
- this.delay = options.delay;
77
-
78
- this.forceReady = this.wrappedForceReady();
79
- this.startModifying = this.wrappedStartModifying();
80
- }
81
-
82
- Watir.prototype.exitOnTimeout = function() {
83
- var that = this;
84
-
85
- Underscore.delay(function() {
86
- if (!that.startedModifying) {
87
- that._forceReady();
37
+ var callNow = immediate && !timeout;
38
+ if (!timeout) {
39
+ timeout = setTimeout(later, wait);
88
40
  }
89
- }, this.delay);
90
- };
91
-
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);
95
- }
96
-
97
- Watir.prototype.removeObservers = function() {
98
- this.unobserve(this.observers.forceReady);
99
- this.unobserve(this.observers.startModifying);
100
- }
101
-
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
41
+ if (callNow) result = func.apply(context, args);
42
+ return result;
111
43
  };
112
- observer.observe(el, config);
113
- return observer;
114
44
  };
115
45
 
116
- Watir.prototype.unobserve = function(observer) {
46
+ /**
47
+ * Disconnects observer and
48
+ * invokes WebDriver's callback function
49
+ * to show that DOM has finished modifying.
50
+ */
51
+ var exitOnModified = _debounce(function() {
52
+ clearTimeout(exitTimeout);
117
53
  observer.disconnect();
118
- };
54
+ exit(exits.modified);
55
+ }, interval);
119
56
 
120
- Watir.prototype.wrappedForceReady = function() {
121
- return Underscore.bind(Underscore.debounce(this._forceReady, this.interval), this);
122
- };
123
-
124
- Watir.prototype.wrappedStartModifying = function() {
125
- return Underscore.bind(Underscore.once(this._startModifying, this.interval), this);
126
- }
127
-
128
- Watir.prototype._forceReady = function(mutations) {
129
- var that = this;
130
-
131
- Underscore.once(function() {
132
- that.removeObservers();
133
- that.domReady -= 1;
134
- })();
57
+ /**
58
+ * Disconnects observer and
59
+ * invokes WebDriver's callback function
60
+ * to show that DOM has started modifying
61
+ * but exceeded timeout.
62
+ */
63
+ var exitOnTimeout = function() {
64
+ return setTimeout(function() {
65
+ observer.disconnect();
66
+ exit(exits.timeout);
67
+ }, timeout);
135
68
  }
136
69
 
137
- Watir.prototype._startModifying = function() {
138
- this.startedModifying = true;
70
+ /**
71
+ * Disconnects observer and
72
+ * invokes WebDriver's callback function
73
+ * to show that DOM has not started modifying.
74
+ */
75
+ var exitNoop = function() {
76
+ setTimeout(function() {
77
+ if (!startedModifying) {
78
+ clearTimeout(exitTimeout);
79
+ observer.disconnect();
80
+ exit(exits.noop);
81
+ }
82
+ }, delay);
139
83
  }
140
84
 
141
- window.watir = new Watir({
142
- el: arguments[0],
143
- interval: arguments[1] * 1000,
144
- delay: arguments[2] * 1000,
85
+ var observer = new MutationObserver(function() {
86
+ if (!startedModifying) startedModifying = true;
87
+ exitOnModified();
145
88
  });
89
+ var config = { attributes: true, childList: true, characterData: true, subtree: true };
90
+ observer.observe(element, config);
91
+ var exitTimeout = exitOnTimeout();
92
+ exitNoop();
@@ -1,5 +1,6 @@
1
1
  require "watir-webdriver"
2
2
  require "watir/dom/wait/version"
3
+ require "watir/dom/decorator"
3
4
  require "watir/dom/elements/element"
4
5
 
5
6
  module Watir
@@ -12,13 +13,9 @@ module Watir
12
13
 
13
14
  class << self
14
15
 
15
- attr_writer :timeout
16
16
  attr_writer :interval
17
17
  attr_writer :delay
18
-
19
- def timeout
20
- @timeout ||= 30
21
- end
18
+ attr_writer :timeout
22
19
 
23
20
  def interval
24
21
  @interval ||= 0.5
@@ -28,6 +25,42 @@ module Watir
28
25
  @delay ||= 1
29
26
  end
30
27
 
28
+ def timeout
29
+ @timeout ||= 30
30
+ end
31
+
32
+ #
33
+ # Waits until DOM is changed.
34
+ # @param [Watir::Element] element
35
+ # @param [Hash] opts
36
+ # @option opts [Float] interval How long to wait between DOM nodes adding/removing in seconds. Defaults to 0.5
37
+ # @option opts [Float] delay How long to wait for DOM modifications to start
38
+ # @option opts [Fixnum] timeout seconds to wait before timing out
39
+ # @api private
40
+ #
41
+
42
+ def wait_for_dom(element, opts, message)
43
+ response = self.rescue do
44
+ js = JAVASCRIPT.dup
45
+ driver = element.browser.driver
46
+ # TODO make sure we rollback to user-defined timeout
47
+ # blocked by https://code.google.com/p/selenium/issues/detail?id=6608
48
+ driver.manage.timeouts.script_timeout = opts[:timeout] + 1
49
+ response = driver.execute_async_script(js, element.wd, opts[:interval], opts[:delay], opts[:timeout])
50
+ driver.manage.timeouts.script_timeout = 1
51
+
52
+ response
53
+ end
54
+ # Response statuses:
55
+ # 0: DOM modifications have started and successfully finished
56
+ # 1: DOM modifications have started but exceeded timeout
57
+ # 2: DOM modifications have not started
58
+ if response == 1
59
+ message = "timed out after #{opts[:timeout]} seconds, #{message}"
60
+ raise Watir::Wait::TimeoutError, message
61
+ end
62
+ end
63
+
31
64
  #
32
65
  # Executes block rescuing all necessary exceptions.
33
66
  # @param [Proc] block
@@ -38,16 +71,16 @@ module Watir
38
71
  block.call
39
72
  rescue Selenium::WebDriver::Error::StaleElementReferenceError, Exception::UnknownObjectException => error
40
73
  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)
74
+ if error.is_a?(Exception::UnknownObjectException) && !error.message.include?(msg)
42
75
  raise error
43
76
  else
44
- # element can become stale, so we just retry DOM waiting
77
+ # element became stale so we just retry DOM waiting
45
78
  retry
46
79
  end
47
80
  rescue Selenium::WebDriver::Error::JavascriptError
48
81
  # 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, ?]
82
+ # correct: [#<Selenium::WebDriver::Element:0x..fd5f048838948a830 id="{bdfa905e-b666-354c-9bf1-4dc693fd15a8}">, 300, 3000] [el, interval, timeout]
83
+ # incorrect: [0.3, 3000, nil] [interval, timeout, ??]
51
84
  # TODO there might be some logic (bug?) in Selenium which does this
52
85
  retry
53
86
  end
@@ -55,38 +88,4 @@ module Watir
55
88
  end # << self
56
89
  end # Wait
57
90
  end # Dom
58
-
59
-
60
- #
61
- # Wraps an Element so that any subsequent method calls are
62
- # put on hold until the DOM subtree is modified within the element.
63
- #
64
-
65
- class WhenDOMChangedDecorator
66
-
67
- def initialize(element, opts, message = nil)
68
- @element = element
69
- @opts = opts
70
- @message = message
71
- @js = Dom::Wait::JAVASCRIPT.dup
72
- end
73
-
74
- def method_missing(m, *args, &block)
75
- unless @element.respond_to?(m)
76
- raise NoMethodError, "undefined method `#{m}' for #{@element.inspect}:#{@element.class}"
77
- end
78
-
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 }
82
-
83
- @element.__send__(m, *args, &block)
84
- end
85
- end
86
-
87
- def respond_to?(*args)
88
- @element.respond_to?(*args)
89
- end
90
-
91
- end # WhenDOMChangedDecorator
92
91
  end # Watir
@@ -1,7 +1,7 @@
1
1
  module Watir
2
2
  module Dom
3
3
  module Wait
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end # Wait
6
6
  end # Dom
7
7
  end # Watir
@@ -34,7 +34,6 @@
34
34
  var el = document.getElementById(id);
35
35
  el.style.opacity = '0.' + value;
36
36
  if (value < 9) {
37
- console.log(value);
38
37
  value++;
39
38
  setTimeout(function() { fadeIn(id, value) }, 400);
40
39
  } else {
@@ -4,18 +4,11 @@ describe Watir::Element do
4
4
  describe "#when_dom_changed" do
5
5
  context "when DOM is changed" do
6
6
  context "when block is not given" do
7
- it "waits using event handler" do
7
+ it "waits using mutation observer" do
8
8
  @browser.button(:id => "quick").click
9
9
  expect(@browser.div.when_dom_changed).to have(20).spans
10
10
  end
11
11
 
12
- it "may be run more than one time" do
13
- 3.times do |i|
14
- @browser.button(:id => "quick").click
15
- expect(@browser.div.when_dom_changed).to have(20 * (i + 1)).spans
16
- end
17
- end
18
-
19
12
  it "waits using custom interval" do
20
13
  @browser.button(:id => "long").click
21
14
  expect(@browser.div.when_dom_changed(:interval => 1.1)).to have(5).spans
@@ -25,10 +18,20 @@ describe Watir::Element do
25
18
  @browser.button(:id => "quick").click
26
19
  expect { @browser.div.when_dom_changed(:timeout => 2).spans }.to raise_error(Watir::Wait::TimeoutError)
27
20
  end
21
+
22
+ context "when run more than one time" do
23
+ it "waits for DOM consecutively" do
24
+ 3.times do |i|
25
+ sleep 1
26
+ @browser.button(:id => "quick").click
27
+ expect(@browser.div.when_dom_changed(:timeout => 5, :delay => 2)).to have(20 * (i + 1)).spans
28
+ end
29
+ end
30
+ end
28
31
  end
29
32
 
30
33
  context "when block given" do
31
- it "waits using event handler" do
34
+ it "waits using mutation observer" do
32
35
  @browser.button(:id => "quick").click
33
36
  @browser.div.when_dom_changed do |div|
34
37
  expect(div).to have(20).spans
metadata CHANGED
@@ -1,78 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: watir-dom-wait
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
5
- prerelease:
4
+ version: 0.2.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Alex Rodionov
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-11-17 00:00:00.000000000 Z
11
+ date: 2014-04-13 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: watir-webdriver
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: bundler
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ~>
31
+ - - "~>"
36
32
  - !ruby/object:Gem::Version
37
33
  version: '1.3'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ~>
38
+ - - "~>"
44
39
  - !ruby/object:Gem::Version
45
40
  version: '1.3'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: rake
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - ">="
52
46
  - !ruby/object:Gem::Version
53
47
  version: '0'
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - ">="
60
53
  - !ruby/object:Gem::Version
61
54
  version: '0'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rspec
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ! '>='
59
+ - - ">="
68
60
  - !ruby/object:Gem::Version
69
61
  version: '0'
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ! '>='
66
+ - - ">="
76
67
  - !ruby/object:Gem::Version
77
68
  version: '0'
78
69
  description: Watir extension providing with DOM-based waiting
@@ -82,13 +73,14 @@ executables: []
82
73
  extensions: []
83
74
  extra_rdoc_files: []
84
75
  files:
85
- - .gitignore
86
- - .travis.yml
76
+ - ".gitignore"
77
+ - ".travis.yml"
87
78
  - Gemfile
88
79
  - LICENSE.txt
89
80
  - README.md
90
81
  - Rakefile
91
82
  - lib/watir-dom-wait.rb
83
+ - lib/watir/dom/decorator.rb
92
84
  - lib/watir/dom/elements/element.rb
93
85
  - lib/watir/dom/extensions/js/waitForDom.js
94
86
  - lib/watir/dom/wait.rb
@@ -100,33 +92,26 @@ files:
100
92
  homepage: https://github.com/p0deje/watir-dom-wait
101
93
  licenses:
102
94
  - MIT
95
+ metadata: {}
103
96
  post_install_message:
104
97
  rdoc_options: []
105
98
  require_paths:
106
99
  - lib
107
100
  required_ruby_version: !ruby/object:Gem::Requirement
108
- none: false
109
101
  requirements:
110
- - - ! '>='
102
+ - - ">="
111
103
  - !ruby/object:Gem::Version
112
104
  version: '0'
113
- segments:
114
- - 0
115
- hash: -2698553052236191813
116
105
  required_rubygems_version: !ruby/object:Gem::Requirement
117
- none: false
118
106
  requirements:
119
- - - ! '>='
107
+ - - ">="
120
108
  - !ruby/object:Gem::Version
121
109
  version: '0'
122
- segments:
123
- - 0
124
- hash: -2698553052236191813
125
110
  requirements: []
126
111
  rubyforge_project:
127
- rubygems_version: 1.8.23
112
+ rubygems_version: 2.2.0
128
113
  signing_key:
129
- specification_version: 3
114
+ specification_version: 4
130
115
  summary: Watir extension providing with DOM-based waiting
131
116
  test_files:
132
117
  - spec/spec_helper.rb