capybara-webkit 1.14.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,8 @@ require "spec_helper"
2
2
  require "capybara/webkit/server"
3
3
 
4
4
  describe Capybara::Webkit::Connection do
5
- it "ensures the process ends", skip_on_windows: true, skip_on_jruby: true do
5
+ it "ensures the process ends when the parent process ends", skip_on_windows: true, skip_on_jruby: true do
6
+ sleep 1 # Without this sleep popen3 hangs on OSX when running the tests - not really sure why
6
7
  read_io, write_io = IO.pipe
7
8
 
8
9
  fork_pid = fork do
@@ -25,6 +26,12 @@ describe Capybara::Webkit::Connection do
25
26
  end
26
27
  end
27
28
 
29
+ it "shouldn't output extraneous warnings when exiting", skip_on_windows: true do
30
+ output_str, status = Open3.capture2e("rspec spec/fixtures/exit_text.rb")
31
+ expect(status.exitstatus).to eq(0)
32
+ expect(output_str).not_to include("AsynchronousCloseException")
33
+ end
34
+
28
35
  it "raises an error if the server has stopped", skip_on_windows: true do
29
36
  path = "false"
30
37
  stub_const("Capybara::Webkit::Server::SERVER_PATH", path)
@@ -82,7 +89,7 @@ describe Capybara::Webkit::Connection do
82
89
  socket.puts script.to_s.bytesize
83
90
  socket.print script
84
91
 
85
- expect(read_io).to include_response "\nhello world"
92
+ expect(read_io).to include_response /\n\d{2}:\d{2}:\d{2}\.\d{3} hello world/
86
93
  end
87
94
 
88
95
  it "does not forward stderr to nil" do
@@ -13,7 +13,7 @@ $webkit_server = Capybara::Webkit::Server.new
13
13
  $webkit_connection = Capybara::Webkit::Connection.new(server: $webkit_server)
14
14
  $webkit_browser = Capybara::Webkit::Browser.new($webkit_connection)
15
15
 
16
- if ENV['DEBUG']
16
+ if ENV["DEBUG"]
17
17
  $webkit_browser.enable_logging
18
18
  end
19
19
 
@@ -61,6 +61,9 @@ RSpec.configure do |c|
61
61
  end
62
62
  )
63
63
  }
64
+
65
+ c.filter_run :focus unless ENV["TRAVIS"]
66
+ c.run_all_when_everything_filtered = true
64
67
  end
65
68
 
66
69
  def with_env_vars(vars)
@@ -8,7 +8,11 @@ RSpec::Matchers.define :include_response do |expected_response|
8
8
 
9
9
  while !found_response && IO.select([read_io], nil, nil, read_timeout) do
10
10
  response += read_io.read_nonblock(read_bytes)
11
- found_response = response.include?(expected_response)
11
+ found_response = if expected_response.is_a? Regexp
12
+ response.match(expected_response)
13
+ else
14
+ response.include?(expected_response)
15
+ end
12
16
  end
13
17
 
14
18
  found_response
@@ -5,6 +5,7 @@
5
5
  #include "Reset.h"
6
6
  #include "Node.h"
7
7
  #include "Evaluate.h"
8
+ #include "EvaluateAsync.h"
8
9
  #include "Execute.h"
9
10
  #include "FrameFocus.h"
10
11
  #include "Header.h"
@@ -46,6 +47,7 @@
46
47
  #include "WindowMaximize.h"
47
48
  #include "GoBack.h"
48
49
  #include "GoForward.h"
50
+ #include "Refresh.h"
49
51
  #include "AcceptAlert.h"
50
52
  #include "FindModal.h"
51
53
  #include "SetUnknownUrlMode.h"
@@ -24,7 +24,7 @@ Connection::Connection(QTcpSocket *socket, WebPageManager *manager, QObject *par
24
24
  }
25
25
 
26
26
  void Connection::commandReady(Command *command) {
27
- m_manager->logger() << "Received" << command->toString();
27
+ m_manager->log() << "Received" << command->toString();
28
28
  startCommand(command);
29
29
  }
30
30
 
@@ -65,7 +65,7 @@ void Connection::writeResponse(Response *response) {
65
65
  else
66
66
  m_socket->write("failure\n");
67
67
 
68
- m_manager->logger() << "Wrote response" << response->isSuccess() << response->message();
68
+ m_manager->log() << "Wrote response" << response->isSuccess() << response->message();
69
69
 
70
70
  QByteArray messageUtf8 = response->message();
71
71
  QString messageLength = QString::number(messageUtf8.size()) + "\n";
@@ -18,8 +18,8 @@ void Evaluate::start() {
18
18
  QString eval_script = QString("(function(){"
19
19
  " for(var i=0; i<arguments.length; i++) {"
20
20
  " arguments[i] = JSON.parse(arguments[i]);"
21
- " var elem_id;"
22
- " if (elem_id = arguments[i]['element-581e-422e-8be1-884c4e116226']) {"
21
+ " var elem_id = arguments[i]['element-581e-422e-8be1-884c4e116226'];"
22
+ " if (elem_id) {"
23
23
  " arguments[i] = Capybara.getNode(elem_id);"
24
24
  " };"
25
25
  " };"
@@ -0,0 +1,54 @@
1
+ #include "EvaluateAsync.h"
2
+ #include "WebPage.h"
3
+ #include "WebPageManager.h"
4
+ #include "JsonSerializer.h"
5
+ #include <iostream>
6
+
7
+ EvaluateAsync::EvaluateAsync(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {
8
+ }
9
+
10
+ void EvaluateAsync::start() {
11
+ QString script = arguments()[0];
12
+ QString jsonArgs;
13
+ if (arguments().length()>1){
14
+ jsonArgs = arguments()[1];
15
+ } else {
16
+ jsonArgs ="[]";
17
+ }
18
+ QString eval_script = QString("(function(){"
19
+ " for(var i=0; i<arguments.length-1; i++) {"
20
+ " arguments[i] = JSON.parse(arguments[i]);"
21
+ " var elem_id = arguments[i]['element-581e-422e-8be1-884c4e116226'];"
22
+ " if (elem_id) {"
23
+ " arguments[i] = Capybara.getNode(elem_id);"
24
+ " };"
25
+ " };"
26
+ " eval(\"%1\");"
27
+ " return;"
28
+ " }).apply(null, %2.concat(function(res){ "
29
+ " res = Capybara.wrapResult(res);"
30
+ // This isn't necessary in Qt 5.5 but is in older (at least 5.2 on Travis)
31
+ " if (res instanceof Array) {"
32
+ " CapybaraAsync['asyncResult(QVariantList)'](res);"
33
+ " } else { "
34
+ " CapybaraAsync['asyncResult(QVariant)'](res);"
35
+ " }"
36
+ " }))").arg(script.replace("\"","\\\"").remove("\n"), jsonArgs);
37
+ QObject invocation_stub;
38
+ invocation_stub.setProperty("allowUnattached", false);
39
+ page()->currentFrame()->addToJavaScriptWindowObject("CapybaraInvocation", &invocation_stub);
40
+ page()->currentFrame()->addToJavaScriptWindowObject("CapybaraAsync", this);
41
+ page()->currentFrame()->evaluateJavaScript(eval_script);
42
+ }
43
+
44
+ void EvaluateAsync::asyncResult(QVariantList result) {
45
+ JsonSerializer serializer;
46
+ finish(true, serializer.serialize(result));
47
+ }
48
+
49
+ void EvaluateAsync::asyncResult(QVariant result) {
50
+ JsonSerializer serializer;
51
+ finish(true, serializer.serialize(result));
52
+ }
53
+
54
+
@@ -0,0 +1,14 @@
1
+ #include "SocketCommand.h"
2
+
3
+ #include <QVariantList>
4
+
5
+ class EvaluateAsync : public SocketCommand {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ EvaluateAsync(WebPageManager *, QStringList &arguments, QObject *parent = 0);
10
+ virtual void start();
11
+ Q_INVOKABLE void asyncResult(QVariant result);
12
+ Q_INVOKABLE void asyncResult(QVariantList result);
13
+ };
14
+
@@ -5,6 +5,8 @@
5
5
  #include <QEvent>
6
6
  #include <QContextMenuEvent>
7
7
 
8
+ QMap<QString, Qt::KeyboardModifiers> JavascriptInvocation::m_modifiersMap(JavascriptInvocation::makeModifiersMap());
9
+
8
10
  JavascriptInvocation::JavascriptInvocation(const QString &functionName, bool allowUnattached, const QStringList &arguments, WebPage *page, QObject *parent) : QObject(parent) {
9
11
  m_functionName = functionName;
10
12
  m_allowUnattached = allowUnattached;
@@ -33,6 +35,7 @@ void JavascriptInvocation::setError(QVariant error) {
33
35
  }
34
36
 
35
37
  InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
38
+
36
39
  frame->addToJavaScriptWindowObject("CapybaraInvocation", this);
37
40
  QVariant result = frame->evaluateJavaScript("Capybara.invoke()");
38
41
  if (getError().isValid())
@@ -41,36 +44,43 @@ InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
41
44
  if (functionName() == "leftClick") {
42
45
  // Don't trigger the left click from JS incase the frame closes
43
46
  QVariantMap qm = result.toMap();
44
- leftClick(qm["absoluteX"].toInt(), qm["absoluteY"].toInt());
47
+ leftClick(qm["absoluteX"].toInt(), qm["absoluteY"].toInt(), qm["keys"].value<QVariantList>());
45
48
  }
46
49
  return InvocationResult(result);
47
50
  }
48
51
  }
49
52
 
50
- void JavascriptInvocation::leftClick(int x, int y) {
53
+ Qt::KeyboardModifiers JavascriptInvocation::modifiers(const QVariantList& keys){
54
+ Qt::KeyboardModifiers modifiers = Qt::NoModifier;
55
+ for (int i = 0; i < keys.length(); i++) {
56
+ modifiers |= m_modifiersMap.value(keys[i].toString(), Qt::NoModifier);
57
+ }
58
+ return modifiers;
59
+ }
60
+
61
+ void JavascriptInvocation::leftClick(int x, int y, QVariantList keys) {
51
62
  QPoint mousePos(x, y);
52
63
 
53
- m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::LeftButton);
54
- m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton);
64
+ m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::LeftButton, modifiers(keys));
65
+ m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton, modifiers(keys));
55
66
  }
56
67
 
57
- void JavascriptInvocation::rightClick(int x, int y) {
68
+ void JavascriptInvocation::rightClick(int x, int y, QVariantList keys) {
58
69
  QPoint mousePos(x, y);
59
-
60
- m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::RightButton);
70
+ m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::RightButton, modifiers(keys));
61
71
 
62
72
  // swallowContextMenuEvent tries to fire contextmenu event in html page
63
- QContextMenuEvent *event = new QContextMenuEvent(QContextMenuEvent::Mouse, mousePos);
73
+ QContextMenuEvent *event = new QContextMenuEvent(QContextMenuEvent::Mouse, mousePos, QCursor::pos(), modifiers(keys));
64
74
  m_page->swallowContextMenuEvent(event);
65
75
 
66
- m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::RightButton);
76
+ m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::RightButton, modifiers(keys));
67
77
  }
68
78
 
69
- void JavascriptInvocation::doubleClick(int x, int y) {
79
+ void JavascriptInvocation::doubleClick(int x, int y, QVariantList keys) {
70
80
  QPoint mousePos(x, y);
71
81
 
72
- m_page->mouseEvent(QEvent::MouseButtonDblClick, mousePos, Qt::LeftButton);
73
- m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton);
82
+ m_page->mouseEvent(QEvent::MouseButtonDblClick, mousePos, Qt::LeftButton, modifiers(keys));
83
+ m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton, modifiers(keys));
74
84
  }
75
85
 
76
86
  bool JavascriptInvocation::clickTest(QWebElement element, int absoluteX, int absoluteY) {
@@ -86,6 +96,8 @@ QVariantMap JavascriptInvocation::clickPosition(QWebElement element, int left, i
86
96
  QVariantMap m;
87
97
  m["relativeX"] = mousePos.x();
88
98
  m["relativeY"] = mousePos.y();
99
+ m["relativeTop"] = boundedBox.top();
100
+ m["relativeLeft"] = boundedBox.left();
89
101
 
90
102
  QWebFrame *parent = element.webFrame();
91
103
  while (parent) {
@@ -95,7 +107,8 @@ QVariantMap JavascriptInvocation::clickPosition(QWebElement element, int left, i
95
107
 
96
108
  boundedBox = elementBox.intersected(viewport);
97
109
  mousePos = boundedBox.center();
98
-
110
+ m["absoluteTop"] = boundedBox.top();
111
+ m["absoluteLeft"] = boundedBox.left();
99
112
  m["absoluteX"] = mousePos.x();
100
113
  m["absoluteY"] = mousePos.y();
101
114
 
@@ -217,3 +230,14 @@ const QString JavascriptInvocation::render(void) {
217
230
  m_page->render(path, QSize(1024, 768));
218
231
  return path;
219
232
  }
233
+
234
+ QMap<QString, Qt::KeyboardModifiers> JavascriptInvocation::makeModifiersMap(){
235
+ QMap<QString, Qt::KeyboardModifiers> map;
236
+ map["alt"] = Qt::AltModifier;
237
+ map["control"] = Qt::ControlModifier;
238
+ map["meta"] = Qt::MetaModifier;
239
+ map["shift"] = Qt::ShiftModifier;
240
+ return map;
241
+ }
242
+
243
+
@@ -20,9 +20,9 @@ class JavascriptInvocation : public QObject {
20
20
  QString &functionName();
21
21
  bool allowUnattached();
22
22
  QStringList &arguments();
23
- Q_INVOKABLE void leftClick(int x, int y);
24
- Q_INVOKABLE void rightClick(int x, int y);
25
- Q_INVOKABLE void doubleClick(int x, int y);
23
+ Q_INVOKABLE void leftClick(int x, int y, QVariantList keys);
24
+ Q_INVOKABLE void rightClick(int x, int y, QVariantList keys);
25
+ Q_INVOKABLE void doubleClick(int x, int y, QVariantList keys);
26
26
  Q_INVOKABLE bool clickTest(QWebElement element, int absoluteX, int absoluteY);
27
27
  Q_INVOKABLE QVariantMap clickPosition(QWebElement element, int left, int top, int width, int height);
28
28
  Q_INVOKABLE void hover(int absoluteX, int absoluteY);
@@ -46,5 +46,8 @@ class JavascriptInvocation : public QObject {
46
46
  int keyCodeForName(const QString &);
47
47
  Qt::Key key_enum;
48
48
  Qt::KeyboardModifiers m_currentModifiers;
49
+ Qt::KeyboardModifiers modifiers(const QVariantList& keys);
50
+ static QMap<QString, Qt::KeyboardModifiers> makeModifiersMap();
51
+ static QMap<QString, Qt::KeyboardModifiers> m_modifiersMap;
49
52
  };
50
53
 
@@ -1,6 +1,7 @@
1
1
  #include "Node.h"
2
2
  #include "WebPage.h"
3
3
  #include "WebPageManager.h"
4
+ #include "JsonSerializer.h"
4
5
  #include "InvocationResult.h"
5
6
 
6
7
  Node::Node(WebPageManager *manager, QStringList &arguments, QObject *parent) : JavascriptCommand(manager, arguments, parent) {
@@ -14,7 +15,14 @@ void Node::start() {
14
15
  if (functionName == "focus_frame") {
15
16
  page()->setCurrentFrameParent(page()->currentFrame()->parentFrame());
16
17
  }
17
- finish(&result);
18
+
19
+ if (result.hasError()) {
20
+ finish(&result);
21
+ } else {
22
+ JsonSerializer serializer;
23
+ InvocationResult jsonResult = InvocationResult(serializer.serialize(result.result()));
24
+ finish(&jsonResult);
25
+ }
18
26
  }
19
27
 
20
28
  QString Node::toString() const {
@@ -14,7 +14,7 @@ PageLoadingCommand::PageLoadingCommand(Command *command, WebPageManager *manager
14
14
  }
15
15
 
16
16
  void PageLoadingCommand::start() {
17
- m_manager->logger() << "Started" << m_command->toString();
17
+ m_manager->log() << "Started" << m_command->toString();
18
18
  connect(m_command, SIGNAL(finished(Response *)), this, SLOT(commandFinished(Response *)));
19
19
  connect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand()));
20
20
  connect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool)));
@@ -26,7 +26,7 @@ void PageLoadingCommand::pendingLoadFinished(bool success) {
26
26
  if (m_pageLoadingFromCommand) {
27
27
  m_pageLoadingFromCommand = false;
28
28
  if (m_pendingResponse) {
29
- m_manager->logger() << "Page load from command finished";
29
+ m_manager->log() << "Page load from command finished";
30
30
  if (m_pageSuccess) {
31
31
  emit finished(m_pendingResponse);
32
32
  } else {
@@ -38,13 +38,13 @@ void PageLoadingCommand::pendingLoadFinished(bool success) {
38
38
  }
39
39
 
40
40
  void PageLoadingCommand::pageLoadingFromCommand() {
41
- m_manager->logger() << m_command->toString() << "started page load";
41
+ m_manager->log() << m_command->toString() << "started page load";
42
42
  m_pageLoadingFromCommand = true;
43
43
  }
44
44
 
45
45
  void PageLoadingCommand::commandFinished(Response *response) {
46
46
  disconnect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand()));
47
- m_manager->logger() << "Finished" << m_command->toString() << "with response" << response->toString();
47
+ m_manager->log() << "Finished" << m_command->toString() << "with response" << response->toString();
48
48
 
49
49
  if (m_pageLoadingFromCommand)
50
50
  m_pendingResponse = response;
@@ -0,0 +1,12 @@
1
+ #include "Refresh.h"
2
+ #include "SocketCommand.h"
3
+ #include "WebPage.h"
4
+ #include "WebPageManager.h"
5
+
6
+ Refresh::Refresh(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {
7
+ }
8
+
9
+ void Refresh::start() {
10
+ page()->triggerAction(QWebPage::Reload);
11
+ finish(true);
12
+ }
@@ -0,0 +1,10 @@
1
+ #include "SocketCommand.h"
2
+
3
+ class Refresh : public SocketCommand {
4
+ Q_OBJECT
5
+
6
+ public:
7
+ Refresh(WebPageManager *, QStringList &arguments, QObject *parent = 0);
8
+ virtual void start();
9
+ };
10
+
@@ -19,7 +19,7 @@ TimeoutCommand::TimeoutCommand(Command *command, WebPageManager *manager, QObjec
19
19
  void TimeoutCommand::start() {
20
20
  QApplication::processEvents();
21
21
  if (m_manager->isLoading()) {
22
- m_manager->logger() << this->toString() << "waiting for load to finish";
22
+ m_manager->log() << this->toString() << "waiting for load to finish";
23
23
  connect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool)));
24
24
  startTimeout();
25
25
  } else {
@@ -25,7 +25,10 @@ WebPage::WebPage(WebPageManager *manager, QObject *parent) : QWebPage(parent) {
25
25
 
26
26
  setForwardUnsupportedContent(true);
27
27
  loadJavascript();
28
- setUserStylesheet();
28
+
29
+ #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
30
+ setUserStylesheet();
31
+ #endif
29
32
 
30
33
  this->setCustomNetworkAccessManager();
31
34
 
@@ -174,7 +177,7 @@ void WebPage::javaScriptConsoleMessage(const QString &message, int lineNumber, c
174
177
  m["line_number"] = lineNumber;
175
178
  }
176
179
  m_consoleMessages.append(m);
177
- m_manager->logger() << qPrintable(fullMessage);
180
+ m_manager->log() << qPrintable(fullMessage);
178
181
  }
179
182
 
180
183
  void WebPage::javaScriptAlert(QWebFrame *frame, const QString &message) {
@@ -191,7 +194,7 @@ void WebPage::javaScriptAlert(QWebFrame *frame, const QString &message) {
191
194
  addModalMessage(expectedType, message, expectedMessage);
192
195
  }
193
196
 
194
- m_manager->logger() << "ALERT:" << qPrintable(message);
197
+ m_manager->log() << "ALERT:" << qPrintable(message);
195
198
  }
196
199
 
197
200
  bool WebPage::javaScriptConfirm(QWebFrame *frame, const QString &message) {
@@ -272,9 +275,9 @@ QString WebPage::failureString() {
272
275
  return message + m_errorPageMessage;
273
276
  }
274
277
 
275
- void WebPage::mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button) {
278
+ void WebPage::mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) {
276
279
  m_mousePosition = position;
277
- QMouseEvent event(type, position, button, button, Qt::NoModifier);
280
+ QMouseEvent event(type, position, button, button, modifiers);
278
281
  QApplication::sendEvent(this, &event);
279
282
  }
280
283
 
@@ -488,5 +491,3 @@ QWebFrame* WebPage::currentFrameParent() {
488
491
  void WebPage::setCurrentFrameParent(QWebFrame* frame) {
489
492
  m_currentFrameParent = frame;
490
493
  }
491
-
492
-