watir-dom-wait 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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