Add support for keys and offset with mouse clicks coming in Capybara 3
This commit is contained in:
parent
5cd1ad0645
commit
1d4dfdaac9
|
@ -6,4 +6,5 @@ end
|
||||||
|
|
||||||
appraise "master" do
|
appraise "master" do
|
||||||
gem "capybara", github: "jnicklas/capybara"
|
gem "capybara", github: "jnicklas/capybara"
|
||||||
|
gem "puma"
|
||||||
end
|
end
|
||||||
|
|
15
Gemfile.lock
15
Gemfile.lock
|
@ -2,7 +2,7 @@ PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
capybara-webkit (1.14.0)
|
capybara-webkit (1.14.0)
|
||||||
capybara (~> 2.3)
|
capybara (>= 2.3, < 4.0)
|
||||||
json
|
json
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
|
@ -14,13 +14,13 @@ GEM
|
||||||
bundler
|
bundler
|
||||||
rake
|
rake
|
||||||
thor (>= 0.14.0)
|
thor (>= 0.14.0)
|
||||||
capybara (2.15.4)
|
capybara (2.17.0)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
rack-test (>= 0.5.4)
|
rack-test (>= 0.5.4)
|
||||||
xpath (~> 2.0)
|
xpath (>= 2.0, < 4.0)
|
||||||
diff-lcs (1.3)
|
diff-lcs (1.3)
|
||||||
ffi (1.9.18-java)
|
ffi (1.9.18-java)
|
||||||
json (1.8.6)
|
json (1.8.6)
|
||||||
|
@ -36,14 +36,11 @@ GEM
|
||||||
mustermann (1.0.1)
|
mustermann (1.0.1)
|
||||||
nokogiri (1.8.1)
|
nokogiri (1.8.1)
|
||||||
mini_portile2 (~> 2.3.0)
|
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)
|
public_suffix (3.0.0)
|
||||||
rack (2.0.3)
|
rack (2.0.3)
|
||||||
rack-protection (2.0.0)
|
rack-protection (2.0.0)
|
||||||
rack
|
rack
|
||||||
rack-test (0.7.0)
|
rack-test (0.8.2)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rake (11.3.0)
|
rake (11.3.0)
|
||||||
rspec (3.7.0)
|
rspec (3.7.0)
|
||||||
|
@ -68,8 +65,8 @@ GEM
|
||||||
ffi
|
ffi
|
||||||
thor (0.20.0)
|
thor (0.20.0)
|
||||||
tilt (2.0.8)
|
tilt (2.0.8)
|
||||||
xpath (2.1.0)
|
xpath (3.0.0)
|
||||||
nokogiri (~> 1.3)
|
nokogiri (~> 1.8)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
java
|
java
|
||||||
|
|
|
@ -21,7 +21,7 @@ Gem::Specification.new do |s|
|
||||||
|
|
||||||
s.requirements << "Qt >= 4.8"
|
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_runtime_dependency("json")
|
||||||
|
|
||||||
s.add_development_dependency("rspec", "~> 3.5")
|
s.add_development_dependency("rspec", "~> 3.5")
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
gem "json", "< 2.0", platforms: [:ruby_19, :jruby_19]
|
|
||||||
gem "capybara", github: "jnicklas/capybara"
|
gem "capybara", github: "jnicklas/capybara"
|
||||||
|
gem "puma"
|
||||||
|
|
||||||
gemspec path: "../"
|
gemspec path: "../"
|
||||||
|
|
|
@ -68,16 +68,16 @@ module Capybara::Webkit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def click
|
def click(keys = [], offset = {})
|
||||||
invoke("leftClick")
|
invoke("leftClick", keys.to_json, offset.to_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
def double_click
|
def double_click(keys = [], offset = {})
|
||||||
invoke("doubleClick")
|
invoke("doubleClick", keys.to_json, offset.to_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
def right_click
|
def right_click(keys = [], offset = {})
|
||||||
invoke("rightClick")
|
invoke("rightClick", keys.to_json, offset.to_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
def hover
|
def hover
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QContextMenuEvent>
|
#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) {
|
JavascriptInvocation::JavascriptInvocation(const QString &functionName, bool allowUnattached, const QStringList &arguments, WebPage *page, QObject *parent) : QObject(parent) {
|
||||||
m_functionName = functionName;
|
m_functionName = functionName;
|
||||||
m_allowUnattached = allowUnattached;
|
m_allowUnattached = allowUnattached;
|
||||||
|
@ -33,6 +35,7 @@ void JavascriptInvocation::setError(QVariant error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
|
InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
|
||||||
|
|
||||||
frame->addToJavaScriptWindowObject("CapybaraInvocation", this);
|
frame->addToJavaScriptWindowObject("CapybaraInvocation", this);
|
||||||
QVariant result = frame->evaluateJavaScript("Capybara.invoke()");
|
QVariant result = frame->evaluateJavaScript("Capybara.invoke()");
|
||||||
if (getError().isValid())
|
if (getError().isValid())
|
||||||
|
@ -41,36 +44,43 @@ InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
|
||||||
if (functionName() == "leftClick") {
|
if (functionName() == "leftClick") {
|
||||||
// Don't trigger the left click from JS incase the frame closes
|
// Don't trigger the left click from JS incase the frame closes
|
||||||
QVariantMap qm = result.toMap();
|
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);
|
return InvocationResult(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void JavascriptInvocation::leftClick(int x, int y) {
|
Qt::KeyboardModifiers JavascriptInvocation::modifiers(const QVariantList& keys){
|
||||||
QPoint mousePos(x, y);
|
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
|
||||||
|
for (int i = 0; i < keys.length(); i++) {
|
||||||
m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::LeftButton);
|
modifiers |= m_modifiersMap.value(keys[i].toString(), Qt::NoModifier);
|
||||||
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton);
|
}
|
||||||
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
void JavascriptInvocation::rightClick(int x, int y) {
|
void JavascriptInvocation::leftClick(int x, int y, QVariantList keys) {
|
||||||
QPoint mousePos(x, y);
|
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
|
// 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->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);
|
QPoint mousePos(x, y);
|
||||||
|
|
||||||
m_page->mouseEvent(QEvent::MouseButtonDblClick, mousePos, Qt::LeftButton);
|
m_page->mouseEvent(QEvent::MouseButtonDblClick, mousePos, Qt::LeftButton, modifiers(keys));
|
||||||
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton);
|
m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton, modifiers(keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JavascriptInvocation::clickTest(QWebElement element, int absoluteX, int absoluteY) {
|
bool JavascriptInvocation::clickTest(QWebElement element, int absoluteX, int absoluteY) {
|
||||||
|
@ -86,6 +96,8 @@ QVariantMap JavascriptInvocation::clickPosition(QWebElement element, int left, i
|
||||||
QVariantMap m;
|
QVariantMap m;
|
||||||
m["relativeX"] = mousePos.x();
|
m["relativeX"] = mousePos.x();
|
||||||
m["relativeY"] = mousePos.y();
|
m["relativeY"] = mousePos.y();
|
||||||
|
m["relativeTop"] = boundedBox.top();
|
||||||
|
m["relativeLeft"] = boundedBox.left();
|
||||||
|
|
||||||
QWebFrame *parent = element.webFrame();
|
QWebFrame *parent = element.webFrame();
|
||||||
while (parent) {
|
while (parent) {
|
||||||
|
@ -95,7 +107,8 @@ QVariantMap JavascriptInvocation::clickPosition(QWebElement element, int left, i
|
||||||
|
|
||||||
boundedBox = elementBox.intersected(viewport);
|
boundedBox = elementBox.intersected(viewport);
|
||||||
mousePos = boundedBox.center();
|
mousePos = boundedBox.center();
|
||||||
|
m["absoluteTop"] = boundedBox.top();
|
||||||
|
m["absoluteLeft"] = boundedBox.left();
|
||||||
m["absoluteX"] = mousePos.x();
|
m["absoluteX"] = mousePos.x();
|
||||||
m["absoluteY"] = mousePos.y();
|
m["absoluteY"] = mousePos.y();
|
||||||
|
|
||||||
|
@ -217,3 +230,14 @@ const QString JavascriptInvocation::render(void) {
|
||||||
m_page->render(path, QSize(1024, 768));
|
m_page->render(path, QSize(1024, 768));
|
||||||
return path;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,9 @@ class JavascriptInvocation : public QObject {
|
||||||
QString &functionName();
|
QString &functionName();
|
||||||
bool allowUnattached();
|
bool allowUnattached();
|
||||||
QStringList &arguments();
|
QStringList &arguments();
|
||||||
Q_INVOKABLE void leftClick(int x, int y);
|
Q_INVOKABLE void leftClick(int x, int y, QVariantList keys);
|
||||||
Q_INVOKABLE void rightClick(int x, int y);
|
Q_INVOKABLE void rightClick(int x, int y, QVariantList keys);
|
||||||
Q_INVOKABLE void doubleClick(int x, int y);
|
Q_INVOKABLE void doubleClick(int x, int y, QVariantList keys);
|
||||||
Q_INVOKABLE bool clickTest(QWebElement element, int absoluteX, int absoluteY);
|
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 QVariantMap clickPosition(QWebElement element, int left, int top, int width, int height);
|
||||||
Q_INVOKABLE void hover(int absoluteX, int absoluteY);
|
Q_INVOKABLE void hover(int absoluteX, int absoluteY);
|
||||||
|
@ -46,5 +46,8 @@ class JavascriptInvocation : public QObject {
|
||||||
int keyCodeForName(const QString &);
|
int keyCodeForName(const QString &);
|
||||||
Qt::Key key_enum;
|
Qt::Key key_enum;
|
||||||
Qt::KeyboardModifiers m_currentModifiers;
|
Qt::KeyboardModifiers m_currentModifiers;
|
||||||
|
Qt::KeyboardModifiers modifiers(const QVariantList& keys);
|
||||||
|
static QMap<QString, Qt::KeyboardModifiers> makeModifiersMap();
|
||||||
|
static QMap<QString, Qt::KeyboardModifiers> m_modifiersMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -275,9 +275,9 @@ QString WebPage::failureString() {
|
||||||
return message + m_errorPageMessage;
|
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;
|
m_mousePosition = position;
|
||||||
QMouseEvent event(type, position, button, button, Qt::NoModifier);
|
QMouseEvent event(type, position, button, button, modifiers);
|
||||||
QApplication::sendEvent(this, &event);
|
QApplication::sendEvent(this, &event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,5 +491,3 @@ QWebFrame* WebPage::currentFrameParent() {
|
||||||
void WebPage::setCurrentFrameParent(QWebFrame* frame) {
|
void WebPage::setCurrentFrameParent(QWebFrame* frame) {
|
||||||
m_currentFrameParent = frame;
|
m_currentFrameParent = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class WebPage : public QWebPage {
|
||||||
QVariantMap pageHeaders();
|
QVariantMap pageHeaders();
|
||||||
QByteArray body();
|
QByteArray body();
|
||||||
QString contentType();
|
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);
|
bool clickTest(QWebElement element, int absoluteX, int absoluteY);
|
||||||
void resize(int, int);
|
void resize(int, int);
|
||||||
int modalCount();
|
int modalCount();
|
||||||
|
|
|
@ -7,7 +7,15 @@ Capybara = {
|
||||||
invoke: function () {
|
invoke: function () {
|
||||||
try {
|
try {
|
||||||
if (CapybaraInvocation.functionName == "leftClick") {
|
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 {
|
} else {
|
||||||
return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
|
return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
|
||||||
}
|
}
|
||||||
|
@ -208,22 +216,28 @@ Capybara = {
|
||||||
return pos;
|
return pos;
|
||||||
},
|
},
|
||||||
|
|
||||||
click: function (index, action) {
|
click: function (index, action, keys, offset) {
|
||||||
var pos = this.verifiedClickPosition(index);
|
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) {
|
leftClick: function (index, keys, offset) {
|
||||||
this.click(index, CapybaraInvocation.leftClick);
|
this.click(index, CapybaraInvocation.leftClick, keys, offset);
|
||||||
},
|
},
|
||||||
|
|
||||||
doubleClick: function(index) {
|
doubleClick: function(index, keys, offset) {
|
||||||
this.click(index, CapybaraInvocation.leftClick);
|
this.click(index, CapybaraInvocation.leftClick, keys, offset);
|
||||||
this.click(index, CapybaraInvocation.doubleClick);
|
this.click(index, CapybaraInvocation.doubleClick, keys, offset);
|
||||||
},
|
},
|
||||||
|
|
||||||
rightClick: function(index) {
|
rightClick: function(index, keys, offset) {
|
||||||
this.click(index, CapybaraInvocation.rightClick);
|
this.click(index, CapybaraInvocation.rightClick, keys, offset);
|
||||||
},
|
},
|
||||||
|
|
||||||
hover: function (index) {
|
hover: function (index) {
|
||||||
|
|
Loading…
Reference in New Issue