Add support for keys and offset with mouse clicks coming in Capybara 3

This commit is contained in:
Thomas Walpole 2018-01-06 12:26:31 -08:00
parent 5cd1ad0645
commit 1d4dfdaac9
10 changed files with 86 additions and 49 deletions

View File

@ -6,4 +6,5 @@ end
appraise "master" do
gem "capybara", github: "jnicklas/capybara"
gem "puma"
end

View File

@ -2,7 +2,7 @@ PATH
remote: .
specs:
capybara-webkit (1.14.0)
capybara (~> 2.3)
capybara (>= 2.3, < 4.0)
json
GEM
@ -14,13 +14,13 @@ GEM
bundler
rake
thor (>= 0.14.0)
capybara (2.15.4)
capybara (2.17.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
xpath (>= 2.0, < 4.0)
diff-lcs (1.3)
ffi (1.9.18-java)
json (1.8.6)
@ -36,14 +36,11 @@ GEM
mustermann (1.0.1)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
nokogiri (1.8.1-java)
nokogiri (1.8.1-x86-mingw32)
mini_portile2 (~> 2.3.0)
public_suffix (3.0.0)
rack (2.0.3)
rack-protection (2.0.0)
rack
rack-test (0.7.0)
rack-test (0.8.2)
rack (>= 1.0, < 3)
rake (11.3.0)
rspec (3.7.0)
@ -68,8 +65,8 @@ GEM
ffi
thor (0.20.0)
tilt (2.0.8)
xpath (2.1.0)
nokogiri (~> 1.3)
xpath (3.0.0)
nokogiri (~> 1.8)
PLATFORMS
java

View File

@ -21,7 +21,7 @@ Gem::Specification.new do |s|
s.requirements << "Qt >= 4.8"
s.add_runtime_dependency("capybara", "~>2.3")
s.add_runtime_dependency("capybara", ">= 2.3", "< 4.0")
s.add_runtime_dependency("json")
s.add_development_dependency("rspec", "~> 3.5")

View File

@ -2,7 +2,7 @@
source "https://rubygems.org"
gem "json", "< 2.0", platforms: [:ruby_19, :jruby_19]
gem "capybara", github: "jnicklas/capybara"
gem "puma"
gemspec path: "../"

View File

@ -68,16 +68,16 @@ module Capybara::Webkit
end
end
def click
invoke("leftClick")
def click(keys = [], offset = {})
invoke("leftClick", keys.to_json, offset.to_json)
end
def double_click
invoke("doubleClick")
def double_click(keys = [], offset = {})
invoke("doubleClick", keys.to_json, offset.to_json)
end
def right_click
invoke("rightClick")
def right_click(keys = [], offset = {})
invoke("rightClick", keys.to_json, offset.to_json)
end
def hover

View File

@ -5,6 +5,8 @@
#include <QEvent>
#include <QContextMenuEvent>
QMap<QString, Qt::KeyboardModifiers> JavascriptInvocation::m_modifiersMap(JavascriptInvocation::makeModifiersMap());
JavascriptInvocation::JavascriptInvocation(const QString &functionName, bool allowUnattached, const QStringList &arguments, WebPage *page, QObject *parent) : QObject(parent) {
m_functionName = functionName;
m_allowUnattached = allowUnattached;
@ -33,6 +35,7 @@ void JavascriptInvocation::setError(QVariant error) {
}
InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
frame->addToJavaScriptWindowObject("CapybaraInvocation", this);
QVariant result = frame->evaluateJavaScript("Capybara.invoke()");
if (getError().isValid())
@ -41,36 +44,43 @@ InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
if (functionName() == "leftClick") {
// Don't trigger the left click from JS incase the frame closes
QVariantMap qm = result.toMap();
leftClick(qm["absoluteX"].toInt(), qm["absoluteY"].toInt());
leftClick(qm["absoluteX"].toInt(), qm["absoluteY"].toInt(), qm["keys"].value<QVariantList>());
}
return InvocationResult(result);
}
}
void JavascriptInvocation::leftClick(int x, int y) {
QPoint mousePos(x, y);
m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::LeftButton);
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton);
Qt::KeyboardModifiers JavascriptInvocation::modifiers(const QVariantList& keys){
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
for (int i = 0; i < keys.length(); i++) {
modifiers |= m_modifiersMap.value(keys[i].toString(), Qt::NoModifier);
}
return modifiers;
}
void JavascriptInvocation::rightClick(int x, int y) {
void JavascriptInvocation::leftClick(int x, int y, QVariantList keys) {
QPoint mousePos(x, y);
m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::RightButton);
m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::LeftButton, modifiers(keys));
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton, modifiers(keys));
}
void JavascriptInvocation::rightClick(int x, int y, QVariantList keys) {
QPoint mousePos(x, y);
m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::RightButton, modifiers(keys));
// swallowContextMenuEvent tries to fire contextmenu event in html page
QContextMenuEvent *event = new QContextMenuEvent(QContextMenuEvent::Mouse, mousePos);
QContextMenuEvent *event = new QContextMenuEvent(QContextMenuEvent::Mouse, mousePos, QCursor::pos(), modifiers(keys));
m_page->swallowContextMenuEvent(event);
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::RightButton);
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::RightButton, modifiers(keys));
}
void JavascriptInvocation::doubleClick(int x, int y) {
void JavascriptInvocation::doubleClick(int x, int y, QVariantList keys) {
QPoint mousePos(x, y);
m_page->mouseEvent(QEvent::MouseButtonDblClick, mousePos, Qt::LeftButton);
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton);
m_page->mouseEvent(QEvent::MouseButtonDblClick, mousePos, Qt::LeftButton, modifiers(keys));
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton, modifiers(keys));
}
bool JavascriptInvocation::clickTest(QWebElement element, int absoluteX, int absoluteY) {
@ -86,6 +96,8 @@ QVariantMap JavascriptInvocation::clickPosition(QWebElement element, int left, i
QVariantMap m;
m["relativeX"] = mousePos.x();
m["relativeY"] = mousePos.y();
m["relativeTop"] = boundedBox.top();
m["relativeLeft"] = boundedBox.left();
QWebFrame *parent = element.webFrame();
while (parent) {
@ -95,7 +107,8 @@ QVariantMap JavascriptInvocation::clickPosition(QWebElement element, int left, i
boundedBox = elementBox.intersected(viewport);
mousePos = boundedBox.center();
m["absoluteTop"] = boundedBox.top();
m["absoluteLeft"] = boundedBox.left();
m["absoluteX"] = mousePos.x();
m["absoluteY"] = mousePos.y();
@ -217,3 +230,14 @@ const QString JavascriptInvocation::render(void) {
m_page->render(path, QSize(1024, 768));
return path;
}
QMap<QString, Qt::KeyboardModifiers> JavascriptInvocation::makeModifiersMap(){
QMap<QString, Qt::KeyboardModifiers> map;
map["alt"] = Qt::AltModifier;
map["control"] = Qt::ControlModifier;
map["meta"] = Qt::MetaModifier;
map["shift"] = Qt::ShiftModifier;
return map;
}

View File

@ -20,9 +20,9 @@ class JavascriptInvocation : public QObject {
QString &functionName();
bool allowUnattached();
QStringList &arguments();
Q_INVOKABLE void leftClick(int x, int y);
Q_INVOKABLE void rightClick(int x, int y);
Q_INVOKABLE void doubleClick(int x, int y);
Q_INVOKABLE void leftClick(int x, int y, QVariantList keys);
Q_INVOKABLE void rightClick(int x, int y, QVariantList keys);
Q_INVOKABLE void doubleClick(int x, int y, QVariantList keys);
Q_INVOKABLE bool clickTest(QWebElement element, int absoluteX, int absoluteY);
Q_INVOKABLE QVariantMap clickPosition(QWebElement element, int left, int top, int width, int height);
Q_INVOKABLE void hover(int absoluteX, int absoluteY);
@ -46,5 +46,8 @@ class JavascriptInvocation : public QObject {
int keyCodeForName(const QString &);
Qt::Key key_enum;
Qt::KeyboardModifiers m_currentModifiers;
Qt::KeyboardModifiers modifiers(const QVariantList& keys);
static QMap<QString, Qt::KeyboardModifiers> makeModifiersMap();
static QMap<QString, Qt::KeyboardModifiers> m_modifiersMap;
};

View File

@ -275,9 +275,9 @@ QString WebPage::failureString() {
return message + m_errorPageMessage;
}
void WebPage::mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button) {
void WebPage::mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) {
m_mousePosition = position;
QMouseEvent event(type, position, button, button, Qt::NoModifier);
QMouseEvent event(type, position, button, button, modifiers);
QApplication::sendEvent(this, &event);
}
@ -491,5 +491,3 @@ QWebFrame* WebPage::currentFrameParent() {
void WebPage::setCurrentFrameParent(QWebFrame* frame) {
m_currentFrameParent = frame;
}

View File

@ -50,7 +50,7 @@ class WebPage : public QWebPage {
QVariantMap pageHeaders();
QByteArray body();
QString contentType();
void mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button);
void mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
bool clickTest(QWebElement element, int absoluteX, int absoluteY);
void resize(int, int);
int modalCount();

View File

@ -7,7 +7,15 @@ Capybara = {
invoke: function () {
try {
if (CapybaraInvocation.functionName == "leftClick") {
return this["verifiedClickPosition"].apply(this, CapybaraInvocation.arguments);
var args = CapybaraInvocation.arguments;
var leftClickOptions = this["verifiedClickPosition"].apply(this, args);
leftClickOptions["keys"] = JSON.parse(args[1]);
offset = JSON.parse(args[2]);
if (offset && offset.x && offset.y){
leftClickOptions["absoluteX"] = leftClickOptions["absoluteLeft"] + offset.x;
leftClickOptions["absoluteY"] = leftClickOptions["absoluteTop"] + offset.y;
}
return leftClickOptions;
} else {
return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
}
@ -208,22 +216,28 @@ Capybara = {
return pos;
},
click: function (index, action) {
click: function (index, action, keys, offset) {
var pos = this.verifiedClickPosition(index);
action(pos.absoluteX, pos.absoluteY);
keys = keys ? JSON.parse(keys) : [];
offset = offset ? JSON.parse(offset) : {};
if (offset && (offset.x != null) && (offset.y != null)){
action(pos.absoluteLeft + offset.x, pos.absoluteTop + offset.y, keys);
} else {
action(pos.absoluteX, pos.absoluteY, keys);
}
},
leftClick: function (index) {
this.click(index, CapybaraInvocation.leftClick);
leftClick: function (index, keys, offset) {
this.click(index, CapybaraInvocation.leftClick, keys, offset);
},
doubleClick: function(index) {
this.click(index, CapybaraInvocation.leftClick);
this.click(index, CapybaraInvocation.doubleClick);
doubleClick: function(index, keys, offset) {
this.click(index, CapybaraInvocation.leftClick, keys, offset);
this.click(index, CapybaraInvocation.doubleClick, keys, offset);
},
rightClick: function(index) {
this.click(index, CapybaraInvocation.rightClick);
rightClick: function(index, keys, offset) {
this.click(index, CapybaraInvocation.rightClick, keys, offset);
},
hover: function (index) {