1
0
Fork 0
mirror of https://github.com/teampoltergeist/poltergeist.git synced 2022-11-09 12:05:00 -05:00

Implement #hover

This commit is contained in:
Jon Leighton 2013-03-03 15:18:05 +00:00
parent 5546d0344c
commit 6c52e69ca1
14 changed files with 88 additions and 66 deletions

View file

@ -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 your CI to automatically re-run failing tests before reporting a failed
build. build.
### ClickFailed errors ### ### MouseEventFailed errors ###
When Poltergeist clicks on an element, rather than generating a DOM When Poltergeist clicks on an element, rather than generating a DOM
click event, it actually generates a "proper" click. This is much closer 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, 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 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 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 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 click. You can then cross-reference this with a screenshot to see if
something is obviously wrong. something is obviously wrong.

View file

@ -123,6 +123,10 @@ module Capybara::Poltergeist
command 'double_click', page_id, id command 'double_click', page_id, id
end end
def hover(page_id, id)
command 'hover', page_id, id
end
def drag(page_id, id, other_id) def drag(page_id, id, other_id)
command 'drag', page_id, id, other_id command 'drag', page_id, id, other_id
end end

View file

@ -239,7 +239,7 @@ class PoltergeistAgent.Node
@element.dispatchEvent(event) @element.dispatchEvent(event)
clickTest: (x, y) -> mouseEventTest: (x, y) ->
frameOffset = this.frameOffset() frameOffset = this.frameOffset()
x -= frameOffset.left x -= frameOffset.left

View file

@ -19,10 +19,10 @@ class Poltergeist.Browser
@page.setViewportSize(width: @width, height: @height) @page.setViewportSize(width: @width, height: @height)
@page.onLoadStarted = => @page.onLoadStarted = =>
this.setState 'loading' if @state == 'clicked' this.setState 'loading' if @state == 'mouse_event'
@page.onNavigationRequested = (url, navigation) => @page.onNavigationRequested = (url, navigation) =>
this.setState 'loading' if @state == 'clicked' && navigation == 'FormSubmitted' this.setState 'loading' if @state == 'mouse_event' && navigation == 'FormSubmitted'
@page.onLoadFinished = (status) => @page.onLoadFinished = (status) =>
if @state == 'loading' if @state == 'loading'
@ -188,24 +188,30 @@ class Poltergeist.Browser
@page = prev_page if prev_page @page = prev_page if prev_page
this.sendResponse(true) 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 # Get the node before changing state, in case there is an exception
node = this.node(page_id, id) 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. # 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 => setTimeout =>
if @state != 'loading' if @state != 'loading'
this.setState 'default' this.setState 'default'
this.sendResponse(@last_click) this.sendResponse(@last_mouse_event)
, 5 , 5
click: (page_id, id) ->
this.mouse_event page_id, id, 'click'
double_click: (page_id, id) -> 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) -> click_coordinates: (x, y) ->
@page.sendEvent('click', x, y) @page.sendEvent('click', x, y)

View file

@ -348,7 +348,7 @@ PoltergeistAgent.Node = (function() {
return this.element.dispatchEvent(event); return this.element.dispatchEvent(event);
}; };
Node.prototype.clickTest = function(x, y) { Node.prototype.mouseEventTest = function(x, y) {
var el, frameOffset, origEl; var el, frameOffset, origEl;
frameOffset = this.frameOffset(); frameOffset = this.frameOffset();
x -= frameOffset.left; x -= frameOffset.left;

View file

@ -25,12 +25,12 @@ Poltergeist.Browser = (function() {
height: this.height height: this.height
}); });
this.page.onLoadStarted = function() { this.page.onLoadStarted = function() {
if (_this.state === 'clicked') { if (_this.state === 'mouse_event') {
return _this.setState('loading'); return _this.setState('loading');
} }
}; };
this.page.onNavigationRequested = function(url, navigation) { this.page.onNavigationRequested = function(url, navigation) {
if (_this.state === 'clicked' && navigation === 'FormSubmitted') { if (_this.state === 'mouse_event' && navigation === 'FormSubmitted') {
return _this.setState('loading'); return _this.setState('loading');
} }
}; };
@ -247,25 +247,30 @@ Poltergeist.Browser = (function() {
return this.sendResponse(true); return this.sendResponse(true);
}; };
Browser.prototype.click = function(page_id, id, event) { Browser.prototype.mouse_event = function(page_id, id, name) {
var node, var node,
_this = this; _this = this;
if (event == null) {
event = 'click';
}
node = this.node(page_id, id); node = this.node(page_id, id);
this.setState('clicked'); this.setState('mouse_event');
this.last_click = node.click(event); this.last_mouse_event = node.mouseEvent(name);
return setTimeout(function() { return setTimeout(function() {
if (_this.state !== 'loading') { if (_this.state !== 'loading') {
_this.setState('default'); _this.setState('default');
return _this.sendResponse(_this.last_click); return _this.sendResponse(_this.last_mouse_event);
} }
}, 5); }, 5);
}; };
Browser.prototype.click = function(page_id, id) {
return this.mouse_event(page_id, id, 'click');
};
Browser.prototype.double_click = function(page_id, id) { 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) { Browser.prototype.click_coordinates = function(x, y) {

View file

@ -104,22 +104,23 @@ Poltergeist.FrameNotFound = (function(_super) {
})(Poltergeist.Error); })(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.selector = selector;
this.position = position; this.position = position;
} }
ClickFailed.prototype.name = "Poltergeist.ClickFailed"; MouseEventFailed.prototype.name = "Poltergeist.MouseEventFailed";
ClickFailed.prototype.args = function() { MouseEventFailed.prototype.args = function() {
return [this.selector, this.position]; return [this.eventName, this.selector, this.position];
}; };
return ClickFailed; return MouseEventFailed;
})(Poltergeist.Error); })(Poltergeist.Error);

View file

@ -4,7 +4,7 @@ Poltergeist.Node = (function() {
var name, _fn, _i, _len, _ref, var name, _fn, _i, _len, _ref,
_this = this; _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) { function Node(page, id) {
this.page = page; this.page = page;
@ -28,7 +28,7 @@ Poltergeist.Node = (function() {
_fn(name); _fn(name);
} }
Node.prototype.clickPosition = function() { Node.prototype.mouseEventPosition = function() {
var middle, pos, viewport; var middle, pos, viewport;
viewport = this.page.viewportSize(); viewport = this.page.viewportSize();
pos = this.position(); pos = this.position();
@ -41,27 +41,24 @@ Poltergeist.Node = (function() {
}; };
}; };
Node.prototype.click = function(event) { Node.prototype.mouseEvent = function(name) {
var pos, test; var pos, test;
if (event == null) {
event = 'click';
}
this.scrollIntoView(); this.scrollIntoView();
pos = this.clickPosition(); pos = this.mouseEventPosition();
test = this.clickTest(pos.x, pos.y); test = this.mouseEventTest(pos.x, pos.y);
if (test.status === 'success') { if (test.status === 'success') {
this.page.mouseEvent(event, pos.x, pos.y); this.page.mouseEvent(name, pos.x, pos.y);
return pos; return pos;
} else { } else {
throw new Poltergeist.ClickFailed(test.selector, pos); throw new Poltergeist.MouseEventFailed(name, test.selector, pos);
} }
}; };
Node.prototype.dragTo = function(other) { Node.prototype.dragTo = function(other) {
var otherPosition, position; var otherPosition, position;
this.scrollIntoView(); this.scrollIntoView();
position = this.clickPosition(); position = this.mouseEventPosition();
otherPosition = other.clickPosition(); otherPosition = other.mouseEventPosition();
this.page.mouseEvent('mousedown', position.x, position.y); this.page.mouseEvent('mousedown', position.x, position.y);
return this.page.mouseEvent('mouseup', otherPosition.x, otherPosition.y); return this.page.mouseEvent('mouseup', otherPosition.x, otherPosition.y);
}; };

View file

@ -55,10 +55,10 @@ class Poltergeist.FrameNotFound extends Poltergeist.Error
name: "Poltergeist.FrameNotFound" name: "Poltergeist.FrameNotFound"
args: -> [@frameName] args: -> [@frameName]
class Poltergeist.ClickFailed extends Poltergeist.Error class Poltergeist.MouseEventFailed extends Poltergeist.Error
constructor: (@selector, @position) -> constructor: (@eventName, @selector, @position) ->
name: "Poltergeist.ClickFailed" name: "Poltergeist.MouseEventFailed"
args: -> [@selector, @position] args: -> [@eventName, @selector, @position]
class Poltergeist.JavascriptError extends Poltergeist.Error class Poltergeist.JavascriptError extends Poltergeist.Error
constructor: (@errors) -> constructor: (@errors) ->

View file

@ -3,7 +3,7 @@
class Poltergeist.Node class Poltergeist.Node
@DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', @DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete',
'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find',
'isVisible', 'position', 'trigger', 'parentId', 'clickTest', 'isVisible', 'position', 'trigger', 'parentId', 'mouseEventTest',
'scrollIntoView', 'isDOMEqual'] 'scrollIntoView', 'isDOMEqual']
constructor: (@page, @id) -> constructor: (@page, @id) ->
@ -16,7 +16,7 @@ class Poltergeist.Node
this.prototype[name] = (args...) -> this.prototype[name] = (args...) ->
@page.nodeCall(@id, name, args) @page.nodeCall(@id, name, args)
clickPosition: -> mouseEventPosition: ->
viewport = @page.viewportSize() viewport = @page.viewportSize()
pos = this.position() pos = this.position()
@ -28,23 +28,23 @@ class Poltergeist.Node
y: middle(pos.top, pos.bottom, viewport.height) y: middle(pos.top, pos.bottom, viewport.height)
} }
click: (event = 'click') -> mouseEvent: (name) ->
this.scrollIntoView() this.scrollIntoView()
pos = this.clickPosition() pos = this.mouseEventPosition()
test = this.clickTest(pos.x, pos.y) test = this.mouseEventTest(pos.x, pos.y)
if test.status == 'success' if test.status == 'success'
@page.mouseEvent(event, pos.x, pos.y) @page.mouseEvent(name, pos.x, pos.y)
pos pos
else else
throw new Poltergeist.ClickFailed(test.selector, pos) throw new Poltergeist.MouseEventFailed(name, test.selector, pos)
dragTo: (other) -> dragTo: (other) ->
this.scrollIntoView() this.scrollIntoView()
position = this.clickPosition() position = this.mouseEventPosition()
otherPosition = other.clickPosition() otherPosition = other.mouseEventPosition()
@page.mouseEvent('mousedown', position.x, position.y) @page.mouseEvent('mousedown', position.x, position.y)
@page.mouseEvent('mouseup', otherPosition.x, otherPosition.y) @page.mouseEvent('mouseup', otherPosition.x, otherPosition.y)

View file

@ -211,7 +211,7 @@ module Capybara::Poltergeist
end end
def invalid_element_errors def invalid_element_errors
[Capybara::Poltergeist::ObsoleteNode, Capybara::Poltergeist::ClickFailed] [Capybara::Poltergeist::ObsoleteNode, Capybara::Poltergeist::MouseEventFailed]
end end
end end
end end

View file

@ -83,19 +83,24 @@ module Capybara
end end
end end
class ClickFailed < NodeError class MouseEventFailed < NodeError
def selector def name
response['args'][0] response['args'][0]
end end
def selector
response['args'][1]
end
def position def position
[response['args'][1]['x'], response['args'][1]['y']] [response['args'][2]['x'], response['args'][2]['y']]
end end
def message 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. " \ "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
end end

View file

@ -22,8 +22,8 @@ module Capybara::Poltergeist
case error.name case error.name
when 'Poltergeist.ObsoleteNode' when 'Poltergeist.ObsoleteNode'
raise ObsoleteNode.new(self, error.response) raise ObsoleteNode.new(self, error.response)
when 'Poltergeist.ClickFailed' when 'Poltergeist.MouseEventFailed'
raise ClickFailed.new(self, error.response) raise MouseEventFailed.new(self, error.response)
else else
raise raise
end end
@ -112,6 +112,10 @@ module Capybara::Poltergeist
command :double_click command :double_click
end end
def hover
command :hover
end
def drag_to(other) def drag_to(other)
command :drag, other.id command :drag, other.id
end end

View file

@ -63,8 +63,8 @@ describe Capybara::Session do
@session.visit("/poltergeist/with_js") @session.visit("/poltergeist/with_js")
end end
it "raises a ClickFailed error" do it "raises a MouseEventFailed error" do
expect { @session.click_link("O hai") }.to raise_error(Capybara::Poltergeist::ClickFailed) expect { @session.click_link("O hai") }.to raise_error(Capybara::Poltergeist::MouseEventFailed)
end end
context "and is then brought in" do context "and is then brought in" do
@ -74,7 +74,7 @@ describe Capybara::Session do
end end
it "clicks properly" do 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 end
after do after do
@ -250,7 +250,7 @@ describe Capybara::Session do
it 'detects if an element is obscured when clicking' do it 'detects if an element is obscured when clicking' do
expect { expect {
@session.find(:css, '#one').click @session.find(:css, '#one').click
}.to raise_error(Capybara::Poltergeist::ClickFailed) }.to raise_error(Capybara::Poltergeist::MouseEventFailed)
begin begin
@session.find(:css, '#one').click @session.find(:css, '#one').click