diff --git a/Appraisals b/Appraisals index 2a2ff5c..3a1ff98 100644 --- a/Appraisals +++ b/Appraisals @@ -6,4 +6,5 @@ end appraise "master" do gem "capybara", github: "jnicklas/capybara" + gem "puma" end diff --git a/Gemfile.lock b/Gemfile.lock index 764d3b4..17066ad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 diff --git a/capybara-webkit.gemspec b/capybara-webkit.gemspec index 121b344..fe59b49 100644 --- a/capybara-webkit.gemspec +++ b/capybara-webkit.gemspec @@ -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") diff --git a/gemfiles/master.gemfile b/gemfiles/master.gemfile index 7a2f2c3..9429e84 100644 --- a/gemfiles/master.gemfile +++ b/gemfiles/master.gemfile @@ -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: "../" diff --git a/lib/capybara/webkit/node.rb b/lib/capybara/webkit/node.rb index 211f921..6c3c685 100644 --- a/lib/capybara/webkit/node.rb +++ b/lib/capybara/webkit/node.rb @@ -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 diff --git a/src/JavascriptInvocation.cpp b/src/JavascriptInvocation.cpp index 2ab1cad..de62f47 100644 --- a/src/JavascriptInvocation.cpp +++ b/src/JavascriptInvocation.cpp @@ -5,6 +5,8 @@ #include #include +QMap 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()); } 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 JavascriptInvocation::makeModifiersMap(){ + QMap map; + map["alt"] = Qt::AltModifier; + map["control"] = Qt::ControlModifier; + map["meta"] = Qt::MetaModifier; + map["shift"] = Qt::ShiftModifier; + return map; +} + + diff --git a/src/JavascriptInvocation.h b/src/JavascriptInvocation.h index 830c3d6..25bac08 100644 --- a/src/JavascriptInvocation.h +++ b/src/JavascriptInvocation.h @@ -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 makeModifiersMap(); + static QMap m_modifiersMap; }; diff --git a/src/WebPage.cpp b/src/WebPage.cpp index 722e618..3b42d1a 100644 --- a/src/WebPage.cpp +++ b/src/WebPage.cpp @@ -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; } - - diff --git a/src/WebPage.h b/src/WebPage.h index 89ea14b..9d5bb43 100644 --- a/src/WebPage.h +++ b/src/WebPage.h @@ -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(); diff --git a/src/capybara.js b/src/capybara.js index 86c11b0..1787e6b 100644 --- a/src/capybara.js +++ b/src/capybara.js @@ -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) {