diff --git a/README.md b/README.md index b09626a..b9ffc90 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ If you experience sporadic crashes a lot, it may be worth configuring your CI to automatically re-run failing tests before reporting a failed build. -### ClickFailed errors ### +### MouseEventFailed errors ### When Poltergeist clicks on an element, rather than generating a DOM click event, it actually generates a "proper" click. This is much closer @@ -251,7 +251,7 @@ your user won't be able to click a covered up element either). Sometimes there can be issues with this behavior. If you have problems, it's worth taking screenshots of the page and trying to work out what's going on. If your click is failing, but you're not getting a -`ClickFailed` error, then you can turn on the `:debug` option and look +`MouseEventFailed` error, then you can turn on the `:debug` option and look in the output to see what co-ordinates Poltergeist is using for the click. You can then cross-reference this with a screenshot to see if something is obviously wrong. diff --git a/lib/capybara/poltergeist/browser.rb b/lib/capybara/poltergeist/browser.rb index adef859..00f5baf 100644 --- a/lib/capybara/poltergeist/browser.rb +++ b/lib/capybara/poltergeist/browser.rb @@ -123,6 +123,10 @@ module Capybara::Poltergeist command 'double_click', page_id, id end + def hover(page_id, id) + command 'hover', page_id, id + end + def drag(page_id, id, other_id) command 'drag', page_id, id, other_id end diff --git a/lib/capybara/poltergeist/client/agent.coffee b/lib/capybara/poltergeist/client/agent.coffee index ce7cd33..a878084 100644 --- a/lib/capybara/poltergeist/client/agent.coffee +++ b/lib/capybara/poltergeist/client/agent.coffee @@ -239,7 +239,7 @@ class PoltergeistAgent.Node @element.dispatchEvent(event) - clickTest: (x, y) -> + mouseEventTest: (x, y) -> frameOffset = this.frameOffset() x -= frameOffset.left diff --git a/lib/capybara/poltergeist/client/browser.coffee b/lib/capybara/poltergeist/client/browser.coffee index e49d3e6..33be351 100644 --- a/lib/capybara/poltergeist/client/browser.coffee +++ b/lib/capybara/poltergeist/client/browser.coffee @@ -19,10 +19,10 @@ class Poltergeist.Browser @page.setViewportSize(width: @width, height: @height) @page.onLoadStarted = => - this.setState 'loading' if @state == 'clicked' + this.setState 'loading' if @state == 'mouse_event' @page.onNavigationRequested = (url, navigation) => - this.setState 'loading' if @state == 'clicked' && navigation == 'FormSubmitted' + this.setState 'loading' if @state == 'mouse_event' && navigation == 'FormSubmitted' @page.onLoadFinished = (status) => if @state == 'loading' @@ -188,24 +188,30 @@ class Poltergeist.Browser @page = prev_page if prev_page this.sendResponse(true) - click: (page_id, id, event = 'click') -> + mouse_event: (page_id, id, name) -> # Get the node before changing state, in case there is an exception node = this.node(page_id, id) - # If the click event triggers onNavigationRequested, we will transition to the 'loading' + # If the event triggers onNavigationRequested, we will transition to the 'loading' # state and wait for onLoadFinished before sending a response. - this.setState 'clicked' + this.setState 'mouse_event' - @last_click = node.click(event) + @last_mouse_event = node.mouseEvent(name) setTimeout => if @state != 'loading' this.setState 'default' - this.sendResponse(@last_click) + this.sendResponse(@last_mouse_event) , 5 + click: (page_id, id) -> + this.mouse_event page_id, id, 'click' + double_click: (page_id, id) -> - this.click page_id, id, 'doubleclick' + this.mouse_event page_id, id, 'doubleclick' + + hover: (page_id, id) -> + this.mouse_event page_id, id, 'mousemove' click_coordinates: (x, y) -> @page.sendEvent('click', x, y) diff --git a/lib/capybara/poltergeist/client/compiled/agent.js b/lib/capybara/poltergeist/client/compiled/agent.js index 3f26aea..25628b0 100644 --- a/lib/capybara/poltergeist/client/compiled/agent.js +++ b/lib/capybara/poltergeist/client/compiled/agent.js @@ -348,7 +348,7 @@ PoltergeistAgent.Node = (function() { return this.element.dispatchEvent(event); }; - Node.prototype.clickTest = function(x, y) { + Node.prototype.mouseEventTest = function(x, y) { var el, frameOffset, origEl; frameOffset = this.frameOffset(); x -= frameOffset.left; diff --git a/lib/capybara/poltergeist/client/compiled/browser.js b/lib/capybara/poltergeist/client/compiled/browser.js index c78ea11..4898d2c 100644 --- a/lib/capybara/poltergeist/client/compiled/browser.js +++ b/lib/capybara/poltergeist/client/compiled/browser.js @@ -25,12 +25,12 @@ Poltergeist.Browser = (function() { height: this.height }); this.page.onLoadStarted = function() { - if (_this.state === 'clicked') { + if (_this.state === 'mouse_event') { return _this.setState('loading'); } }; this.page.onNavigationRequested = function(url, navigation) { - if (_this.state === 'clicked' && navigation === 'FormSubmitted') { + if (_this.state === 'mouse_event' && navigation === 'FormSubmitted') { return _this.setState('loading'); } }; @@ -247,25 +247,30 @@ Poltergeist.Browser = (function() { return this.sendResponse(true); }; - Browser.prototype.click = function(page_id, id, event) { + Browser.prototype.mouse_event = function(page_id, id, name) { var node, _this = this; - if (event == null) { - event = 'click'; - } node = this.node(page_id, id); - this.setState('clicked'); - this.last_click = node.click(event); + this.setState('mouse_event'); + this.last_mouse_event = node.mouseEvent(name); return setTimeout(function() { if (_this.state !== 'loading') { _this.setState('default'); - return _this.sendResponse(_this.last_click); + return _this.sendResponse(_this.last_mouse_event); } }, 5); }; + Browser.prototype.click = function(page_id, id) { + return this.mouse_event(page_id, id, 'click'); + }; + Browser.prototype.double_click = function(page_id, id) { - return this.click(page_id, id, 'doubleclick'); + return this.mouse_event(page_id, id, 'doubleclick'); + }; + + Browser.prototype.hover = function(page_id, id) { + return this.mouse_event(page_id, id, 'mousemove'); }; Browser.prototype.click_coordinates = function(x, y) { diff --git a/lib/capybara/poltergeist/client/compiled/main.js b/lib/capybara/poltergeist/client/compiled/main.js index e1ecdb2..da09328 100644 --- a/lib/capybara/poltergeist/client/compiled/main.js +++ b/lib/capybara/poltergeist/client/compiled/main.js @@ -104,22 +104,23 @@ Poltergeist.FrameNotFound = (function(_super) { })(Poltergeist.Error); -Poltergeist.ClickFailed = (function(_super) { +Poltergeist.MouseEventFailed = (function(_super) { - __extends(ClickFailed, _super); + __extends(MouseEventFailed, _super); - function ClickFailed(selector, position) { + function MouseEventFailed(eventName, selector, position) { + this.eventName = eventName; this.selector = selector; this.position = position; } - ClickFailed.prototype.name = "Poltergeist.ClickFailed"; + MouseEventFailed.prototype.name = "Poltergeist.MouseEventFailed"; - ClickFailed.prototype.args = function() { - return [this.selector, this.position]; + MouseEventFailed.prototype.args = function() { + return [this.eventName, this.selector, this.position]; }; - return ClickFailed; + return MouseEventFailed; })(Poltergeist.Error); diff --git a/lib/capybara/poltergeist/client/compiled/node.js b/lib/capybara/poltergeist/client/compiled/node.js index 48cda76..7141886 100644 --- a/lib/capybara/poltergeist/client/compiled/node.js +++ b/lib/capybara/poltergeist/client/compiled/node.js @@ -4,7 +4,7 @@ Poltergeist.Node = (function() { var name, _fn, _i, _len, _ref, _this = this; - Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'clickTest', 'scrollIntoView', 'isDOMEqual']; + Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual']; function Node(page, id) { this.page = page; @@ -28,7 +28,7 @@ Poltergeist.Node = (function() { _fn(name); } - Node.prototype.clickPosition = function() { + Node.prototype.mouseEventPosition = function() { var middle, pos, viewport; viewport = this.page.viewportSize(); pos = this.position(); @@ -41,27 +41,24 @@ Poltergeist.Node = (function() { }; }; - Node.prototype.click = function(event) { + Node.prototype.mouseEvent = function(name) { var pos, test; - if (event == null) { - event = 'click'; - } this.scrollIntoView(); - pos = this.clickPosition(); - test = this.clickTest(pos.x, pos.y); + pos = this.mouseEventPosition(); + test = this.mouseEventTest(pos.x, pos.y); if (test.status === 'success') { - this.page.mouseEvent(event, pos.x, pos.y); + this.page.mouseEvent(name, pos.x, pos.y); return pos; } else { - throw new Poltergeist.ClickFailed(test.selector, pos); + throw new Poltergeist.MouseEventFailed(name, test.selector, pos); } }; Node.prototype.dragTo = function(other) { var otherPosition, position; this.scrollIntoView(); - position = this.clickPosition(); - otherPosition = other.clickPosition(); + position = this.mouseEventPosition(); + otherPosition = other.mouseEventPosition(); this.page.mouseEvent('mousedown', position.x, position.y); return this.page.mouseEvent('mouseup', otherPosition.x, otherPosition.y); }; diff --git a/lib/capybara/poltergeist/client/main.coffee b/lib/capybara/poltergeist/client/main.coffee index 116767e..2ff7adc 100644 --- a/lib/capybara/poltergeist/client/main.coffee +++ b/lib/capybara/poltergeist/client/main.coffee @@ -55,10 +55,10 @@ class Poltergeist.FrameNotFound extends Poltergeist.Error name: "Poltergeist.FrameNotFound" args: -> [@frameName] -class Poltergeist.ClickFailed extends Poltergeist.Error - constructor: (@selector, @position) -> - name: "Poltergeist.ClickFailed" - args: -> [@selector, @position] +class Poltergeist.MouseEventFailed extends Poltergeist.Error + constructor: (@eventName, @selector, @position) -> + name: "Poltergeist.MouseEventFailed" + args: -> [@eventName, @selector, @position] class Poltergeist.JavascriptError extends Poltergeist.Error constructor: (@errors) -> diff --git a/lib/capybara/poltergeist/client/node.coffee b/lib/capybara/poltergeist/client/node.coffee index 853083d..e761cf5 100644 --- a/lib/capybara/poltergeist/client/node.coffee +++ b/lib/capybara/poltergeist/client/node.coffee @@ -3,7 +3,7 @@ class Poltergeist.Node @DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', - 'isVisible', 'position', 'trigger', 'parentId', 'clickTest', + 'isVisible', 'position', 'trigger', 'parentId', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual'] constructor: (@page, @id) -> @@ -16,7 +16,7 @@ class Poltergeist.Node this.prototype[name] = (args...) -> @page.nodeCall(@id, name, args) - clickPosition: -> + mouseEventPosition: -> viewport = @page.viewportSize() pos = this.position() @@ -28,23 +28,23 @@ class Poltergeist.Node y: middle(pos.top, pos.bottom, viewport.height) } - click: (event = 'click') -> + mouseEvent: (name) -> this.scrollIntoView() - pos = this.clickPosition() - test = this.clickTest(pos.x, pos.y) + pos = this.mouseEventPosition() + test = this.mouseEventTest(pos.x, pos.y) if test.status == 'success' - @page.mouseEvent(event, pos.x, pos.y) + @page.mouseEvent(name, pos.x, pos.y) pos else - throw new Poltergeist.ClickFailed(test.selector, pos) + throw new Poltergeist.MouseEventFailed(name, test.selector, pos) dragTo: (other) -> this.scrollIntoView() - position = this.clickPosition() - otherPosition = other.clickPosition() + position = this.mouseEventPosition() + otherPosition = other.mouseEventPosition() @page.mouseEvent('mousedown', position.x, position.y) @page.mouseEvent('mouseup', otherPosition.x, otherPosition.y) diff --git a/lib/capybara/poltergeist/driver.rb b/lib/capybara/poltergeist/driver.rb index 10e3073..2e325df 100644 --- a/lib/capybara/poltergeist/driver.rb +++ b/lib/capybara/poltergeist/driver.rb @@ -211,7 +211,7 @@ module Capybara::Poltergeist end def invalid_element_errors - [Capybara::Poltergeist::ObsoleteNode, Capybara::Poltergeist::ClickFailed] + [Capybara::Poltergeist::ObsoleteNode, Capybara::Poltergeist::MouseEventFailed] end end end diff --git a/lib/capybara/poltergeist/errors.rb b/lib/capybara/poltergeist/errors.rb index 85ea02b..0bdcc1f 100644 --- a/lib/capybara/poltergeist/errors.rb +++ b/lib/capybara/poltergeist/errors.rb @@ -83,19 +83,24 @@ module Capybara end end - class ClickFailed < NodeError - def selector + class MouseEventFailed < NodeError + def name response['args'][0] end + def selector + response['args'][1] + end + def position - [response['args'][1]['x'], response['args'][1]['y']] + [response['args'][2]['x'], response['args'][2]['y']] end def message - "Click at co-ordinates [#{position.join(', ')}] failed. Poltergeist detected " \ + "Firing a #{name} at co-ordinates [#{position.join(', ')}] failed. Poltergeist detected " \ "another element with CSS selector '#{selector}' at this position. " \ - "It may be overlapping the element you are trying to click." + "It may be overlapping the element you are trying to interact with. " \ + "If you don't care about overlapping elements, try using node.trigger('#{name}')." end end diff --git a/lib/capybara/poltergeist/node.rb b/lib/capybara/poltergeist/node.rb index 6a4ecfe..f4ca52b 100644 --- a/lib/capybara/poltergeist/node.rb +++ b/lib/capybara/poltergeist/node.rb @@ -22,8 +22,8 @@ module Capybara::Poltergeist case error.name when 'Poltergeist.ObsoleteNode' raise ObsoleteNode.new(self, error.response) - when 'Poltergeist.ClickFailed' - raise ClickFailed.new(self, error.response) + when 'Poltergeist.MouseEventFailed' + raise MouseEventFailed.new(self, error.response) else raise end @@ -112,6 +112,10 @@ module Capybara::Poltergeist command :double_click end + def hover + command :hover + end + def drag_to(other) command :drag, other.id end diff --git a/spec/integration/session_spec.rb b/spec/integration/session_spec.rb index ef3097e..aed4de9 100644 --- a/spec/integration/session_spec.rb +++ b/spec/integration/session_spec.rb @@ -63,8 +63,8 @@ describe Capybara::Session do @session.visit("/poltergeist/with_js") end - it "raises a ClickFailed error" do - expect { @session.click_link("O hai") }.to raise_error(Capybara::Poltergeist::ClickFailed) + it "raises a MouseEventFailed error" do + expect { @session.click_link("O hai") }.to raise_error(Capybara::Poltergeist::MouseEventFailed) end context "and is then brought in" do @@ -74,7 +74,7 @@ describe Capybara::Session do end it "clicks properly" do - expect { @session.click_link "O hai" }.to_not raise_error(Capybara::Poltergeist::ClickFailed) + expect { @session.click_link "O hai" }.to_not raise_error(Capybara::Poltergeist::MouseEventFailed) end after do @@ -250,7 +250,7 @@ describe Capybara::Session do it 'detects if an element is obscured when clicking' do expect { @session.find(:css, '#one').click - }.to raise_error(Capybara::Poltergeist::ClickFailed) + }.to raise_error(Capybara::Poltergeist::MouseEventFailed) begin @session.find(:css, '#one').click