mirror of
https://github.com/teampoltergeist/poltergeist.git
synced 2022-11-09 12:05:00 -05:00
Implement #hover
This commit is contained in:
parent
5546d0344c
commit
6c52e69ca1
14 changed files with 88 additions and 66 deletions
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue