capybara-webkit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -6,17 +6,17 @@ require 'capybara_webkit_builder'
6
6
 
7
7
  desc "Generate a Makefile using qmake"
8
8
  file 'Makefile' do
9
- CapybaraWebkitBuilder.makefile
9
+ CapybaraWebkitBuilder.makefile or exit(1)
10
10
  end
11
11
 
12
12
  desc "Regenerate dependencies using qmake"
13
13
  task :qmake => 'Makefile' do
14
- CapybaraWebkitBuilder.qmake
14
+ CapybaraWebkitBuilder.qmake or exit(1)
15
15
  end
16
16
 
17
17
  desc "Build the webkit server"
18
18
  task :build => :qmake do
19
- CapybaraWebkitBuilder.build
19
+ CapybaraWebkitBuilder.build or exit(1)
20
20
  end
21
21
 
22
22
  file 'bin/webkit_server' => :build
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "capybara-webkit"
3
- s.version = "0.2.0"
3
+ s.version = "0.3.0"
4
4
  s.authors = ["thoughtbot", "Joe Ferris", "Jason Morrison", "Tristan Dunn",
5
5
  "Joshua Clayton", "Yuichi Tateno", "Aaron Gibralter"]
6
6
  s.email = "support@thoughtbot.com"
@@ -42,9 +42,7 @@ class Capybara::Driver::Webkit
42
42
  end
43
43
 
44
44
  def drag_to(element)
45
- trigger('mousedown')
46
- element.trigger('mousemove')
47
- element.trigger('mouseup')
45
+ invoke 'dragTo', element.native
48
46
  end
49
47
 
50
48
  def tag_name
@@ -84,4 +82,3 @@ class Capybara::Driver::Webkit
84
82
  end
85
83
  end
86
84
  end
87
-
@@ -12,7 +12,7 @@ module CapybaraWebkitBuilder
12
12
  end
13
13
 
14
14
  def build
15
- system("make")
15
+ system("make") or return false
16
16
 
17
17
  FileUtils.mkdir("bin") unless File.directory?("bin")
18
18
 
@@ -24,8 +24,8 @@ module CapybaraWebkitBuilder
24
24
  end
25
25
 
26
26
  def build_all
27
- makefile
28
- qmake
27
+ makefile &&
28
+ qmake &&
29
29
  build
30
30
  end
31
31
  end
data/spec/driver_spec.rb CHANGED
@@ -497,7 +497,7 @@ describe Capybara::Driver::Webkit do
497
497
 
498
498
  draggable.drag_to(container)
499
499
 
500
- subject.find("//*[@class='triggered']").size.should == 2
500
+ subject.find("//*[@class='triggered']").size.should == 1
501
501
  end
502
502
  end
503
503
 
@@ -546,6 +546,67 @@ describe Capybara::Driver::Webkit do
546
546
  end
547
547
  end
548
548
 
549
+ context "error app" do
550
+ before(:all) do
551
+ @app = lambda do |env|
552
+ if env['PATH_INFO'] == "/error"
553
+ [404, {}, []]
554
+ else
555
+ body = <<-HTML
556
+ <html><body>
557
+ <form action="/error"><input type="submit"/></form>
558
+ </body></html>
559
+ HTML
560
+ [200,
561
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
562
+ [body]]
563
+ end
564
+ end
565
+ end
566
+
567
+ it "raises a webkit error for the requested url" do
568
+ expect {
569
+ subject.find("//input").first.click
570
+ wait_for_error_to_complete
571
+ subject.find("//body")
572
+ }.
573
+ to raise_error(Capybara::Driver::Webkit::WebkitError, %r{/error})
574
+ end
575
+
576
+ def wait_for_error_to_complete
577
+ sleep(0.5)
578
+ end
579
+ end
580
+
581
+ context "slow error app" do
582
+ before(:all) do
583
+ @app = lambda do |env|
584
+ if env['PATH_INFO'] == "/error"
585
+ body = "error"
586
+ sleep(1)
587
+ [304, {}, []]
588
+ else
589
+ body = <<-HTML
590
+ <html><body>
591
+ <form action="/error"><input type="submit"/></form>
592
+ <p>hello</p>
593
+ </body></html>
594
+ HTML
595
+ [200,
596
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
597
+ [body]]
598
+ end
599
+ end
600
+ end
601
+
602
+ it "raises a webkit error and then continues" do
603
+ subject.find("//input").first.click
604
+ expect { subject.find("//p") }.to raise_error(Capybara::Driver::Webkit::WebkitError)
605
+ subject.visit("/")
606
+ subject.find("//p").first.text.should == "hello"
607
+ end
608
+ end
609
+
549
610
  context "popup app" do
550
611
  before(:all) do
551
612
  @app = lambda do |env|
@@ -9,9 +9,7 @@ describe Capybara::Driver::Webkit do
9
9
  # TODO: select options
10
10
  # it_should_behave_like "driver"
11
11
 
12
- # TODO: bug with drag and drop
13
- # it_should_behave_like "driver with javascript support"
14
-
12
+ it_should_behave_like "driver with javascript support"
15
13
  it_should_behave_like "driver with cookies support"
16
14
 
17
15
  # Can't support:
data/src/Command.h CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include <QObject>
5
5
  #include <QStringList>
6
+ #include "Response.h"
6
7
 
7
8
  class WebPage;
8
9
 
@@ -14,7 +15,7 @@ class Command : public QObject {
14
15
  virtual void start(QStringList &arguments);
15
16
 
16
17
  signals:
17
- void finished(bool success, QString &response);
18
+ void finished(Response *response);
18
19
 
19
20
  protected:
20
21
  WebPage *page();
data/src/Connection.cpp CHANGED
@@ -20,7 +20,10 @@ Connection::Connection(QTcpSocket *socket, WebPage *page, QObject *parent) :
20
20
  m_page = page;
21
21
  m_command = NULL;
22
22
  m_expectingDataSize = -1;
23
+ m_pageSuccess = true;
24
+ m_commandWaiting = false;
23
25
  connect(m_socket, SIGNAL(readyRead()), this, SLOT(checkNext()));
26
+ connect(m_page, SIGNAL(loadFinished(bool)), this, SLOT(pendingLoadFinished(bool)));
24
27
  }
25
28
 
26
29
  void Connection::checkNext() {
@@ -75,25 +78,32 @@ void Connection::processArgument(const char *data) {
75
78
 
76
79
  if (m_arguments.length() == m_argumentsExpected) {
77
80
  if (m_page->isLoading())
78
- connect(m_page, SIGNAL(loadFinished(bool)), this, SLOT(pendingLoadFinished(bool)));
81
+ m_commandWaiting = true;
79
82
  else
80
83
  startCommand();
81
84
  }
82
85
  }
83
86
 
84
87
  void Connection::startCommand() {
85
- m_command = createCommand(m_commandName.toAscii().constData());
86
- if (m_command) {
87
- connect(m_command,
88
- SIGNAL(finished(bool, QString &)),
89
- this,
90
- SLOT(finishCommand(bool, QString &)));
91
- m_command->start(m_arguments);
88
+ m_commandWaiting = false;
89
+ if (m_pageSuccess) {
90
+ m_command = createCommand(m_commandName.toAscii().constData());
91
+ if (m_command) {
92
+ connect(m_command,
93
+ SIGNAL(finished(Response *)),
94
+ this,
95
+ SLOT(finishCommand(Response *)));
96
+ m_command->start(m_arguments);
97
+ } else {
98
+ QString failure = QString("Unknown command: ") + m_commandName + "\n";
99
+ writeResponse(new Response(false, failure));
100
+ }
101
+ m_commandName = QString();
92
102
  } else {
93
- QString failure = QString("Unknown command: ") + m_commandName + "\n";
94
- writeResponse(false, failure);
103
+ m_pageSuccess = true;
104
+ QString message = m_page->failureString();
105
+ writeResponse(new Response(false, message));
95
106
  }
96
- m_commandName = QString();
97
107
  }
98
108
 
99
109
  Command *Connection::createCommand(const char *name) {
@@ -102,31 +112,31 @@ Command *Connection::createCommand(const char *name) {
102
112
  }
103
113
 
104
114
  void Connection::pendingLoadFinished(bool success) {
105
- m_page->disconnect(this, SLOT(pendingLoadFinished(bool)));
106
- if (success) {
115
+ m_pageSuccess = success;
116
+ if (m_commandWaiting)
107
117
  startCommand();
108
- } else {
109
- QString response = m_page->failureString();
110
- finishCommand(false, response);
111
- }
112
118
  }
113
119
 
114
- void Connection::finishCommand(bool success, QString &response) {
120
+ void Connection::finishCommand(Response *response) {
115
121
  m_command->deleteLater();
116
122
  m_command = NULL;
117
- m_arguments.clear();
118
- writeResponse(success, response);
123
+ writeResponse(response);
119
124
  }
120
125
 
121
- void Connection::writeResponse(bool success, QString &response) {
122
- if (success)
126
+ void Connection::writeResponse(Response *response) {
127
+ if (response->isSuccess())
123
128
  m_socket->write("ok\n");
124
129
  else
125
130
  m_socket->write("failure\n");
126
131
 
127
- QByteArray response_utf8 = response.toUtf8();
128
- QString responseLength = QString::number(response_utf8.size()) + "\n";
129
- m_socket->write(responseLength.toAscii());
130
- m_socket->write(response_utf8);
132
+ QByteArray messageUtf8 = response->message().toUtf8();
133
+ QString messageLength = QString::number(messageUtf8.size()) + "\n";
134
+ m_socket->write(messageLength.toAscii());
135
+ m_socket->write(messageUtf8);
136
+ delete response;
137
+
138
+ m_arguments.clear();
139
+ m_commandName = QString();
140
+ m_argumentsExpected = -1;
131
141
  }
132
142
 
data/src/Connection.h CHANGED
@@ -4,6 +4,7 @@
4
4
  class QTcpSocket;
5
5
  class WebPage;
6
6
  class Command;
7
+ class Response;
7
8
 
8
9
  class Connection : public QObject {
9
10
  Q_OBJECT
@@ -13,7 +14,7 @@ class Connection : public QObject {
13
14
 
14
15
  public slots:
15
16
  void checkNext();
16
- void finishCommand(bool success, QString &response);
17
+ void finishCommand(Response *response);
17
18
  void pendingLoadFinished(bool success);
18
19
 
19
20
  private:
@@ -23,7 +24,7 @@ class Connection : public QObject {
23
24
  Command *createCommand(const char *name);
24
25
  void processArgument(const char *line);
25
26
  void startCommand();
26
- void writeResponse(bool success, QString &response);
27
+ void writeResponse(Response *response);
27
28
 
28
29
  QTcpSocket *m_socket;
29
30
  QString m_commandName;
@@ -32,5 +33,7 @@ class Connection : public QObject {
32
33
  int m_argumentsExpected;
33
34
  WebPage *m_page;
34
35
  int m_expectingDataSize;
36
+ bool m_pageSuccess;
37
+ bool m_commandWaiting;
35
38
  };
36
39
 
data/src/Evaluate.cpp CHANGED
@@ -9,7 +9,7 @@ Evaluate::Evaluate(WebPage *page, QObject *parent) : Command(page, parent) {
9
9
  void Evaluate::start(QStringList &arguments) {
10
10
  QVariant result = page()->currentFrame()->evaluateJavaScript(arguments[0]);
11
11
  addVariant(result);
12
- emit finished(true, m_buffer);
12
+ emit finished(new Response(true, m_buffer));
13
13
  }
14
14
 
15
15
  void Evaluate::addVariant(QVariant &object) {
data/src/Execute.cpp CHANGED
@@ -7,12 +7,10 @@ Execute::Execute(WebPage *page, QObject *parent) : Command(page, parent) {
7
7
  void Execute::start(QStringList &arguments) {
8
8
  QString script = arguments[0] + QString("; 'success'");
9
9
  QVariant result = page()->currentFrame()->evaluateJavaScript(script);
10
- QString response;
11
10
  if (result.isValid()) {
12
- emit finished(true, response);
11
+ emit finished(new Response(true));
13
12
  } else {
14
- response = "Javascript failed to execute";
15
- emit finished(false, response);
13
+ emit finished(new Response(false, "Javascript failed to execute"));
16
14
  }
17
15
  }
18
16
 
data/src/Find.cpp CHANGED
@@ -6,15 +6,14 @@ Find::Find(WebPage *page, QObject *parent) : Command(page, parent) {
6
6
  }
7
7
 
8
8
  void Find::start(QStringList &arguments) {
9
- QString response;
9
+ QString message;
10
10
  QVariant result = page()->invokeCapybaraFunction("find", arguments);
11
11
 
12
12
  if (result.isValid()) {
13
- response = result.toString();
14
- emit finished(true, response);
13
+ message = result.toString();
14
+ emit finished(new Response(true, message));
15
15
  } else {
16
- response = "Invalid XPath expression";
17
- emit finished(false, response);
16
+ emit finished(new Response(false, "Invalid XPath expression"));
18
17
  }
19
18
  }
20
19
 
data/src/FrameFocus.cpp CHANGED
@@ -50,8 +50,7 @@ void FrameFocus::focusId(QString name) {
50
50
 
51
51
  void FrameFocus::focusParent() {
52
52
  if (page()->currentFrame()->parentFrame() == 0) {
53
- QString response = "Already at parent frame.";
54
- emit finished(false, response);
53
+ emit finished(new Response(false, "Already at parent frame."));
55
54
  } else {
56
55
  page()->currentFrame()->parentFrame()->setFocus();
57
56
  success();
@@ -59,11 +58,9 @@ void FrameFocus::focusParent() {
59
58
  }
60
59
 
61
60
  void FrameFocus::frameNotFound() {
62
- QString response = "Unable to locate frame. ";
63
- emit finished(false, response);
61
+ emit finished(new Response(false, "Unable to locate frame. "));
64
62
  }
65
63
 
66
64
  void FrameFocus::success() {
67
- QString response;
68
- emit finished(true, response);
65
+ emit finished(new Response(true));
69
66
  }
data/src/Node.cpp CHANGED
@@ -9,6 +9,6 @@ void Node::start(QStringList &arguments) {
9
9
  QString functionName = functionArguments.takeFirst();
10
10
  QVariant result = page()->invokeCapybaraFunction(functionName, functionArguments);
11
11
  QString attributeValue = result.toString();
12
- emit finished(true, attributeValue);
12
+ emit finished(new Response(true, attributeValue));
13
13
  }
14
14
 
data/src/Reset.cpp CHANGED
@@ -10,7 +10,6 @@ void Reset::start(QStringList &arguments) {
10
10
  page()->triggerAction(QWebPage::Stop);
11
11
  page()->currentFrame()->setHtml("<html><body></body></html>");
12
12
  page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar());
13
- QString response = "";
14
- emit finished(true, response);
13
+ emit finished(new Response(true));
15
14
  }
16
15
 
data/src/Response.cpp ADDED
@@ -0,0 +1,19 @@
1
+ #include "Response.h"
2
+ #include <iostream>
3
+
4
+ Response::Response(bool success, QString message) {
5
+ m_success = success;
6
+ m_message = message;
7
+ }
8
+
9
+ Response::Response(bool success) {
10
+ m_success = success;
11
+ }
12
+
13
+ bool Response::isSuccess() const {
14
+ return m_success;
15
+ }
16
+
17
+ QString Response::message() const {
18
+ return m_message;
19
+ }
data/src/Response.h ADDED
@@ -0,0 +1,13 @@
1
+ #include <QString>
2
+
3
+ class Response {
4
+ public:
5
+ Response(bool success, QString message);
6
+ Response(bool success);
7
+ bool isSuccess() const;
8
+ QString message() const;
9
+
10
+ private:
11
+ bool m_success;
12
+ QString m_message;
13
+ };
data/src/Source.cpp CHANGED
@@ -7,8 +7,7 @@ Source::Source(WebPage *page, QObject *parent) : Command(page, parent) {
7
7
  void Source::start(QStringList &arguments) {
8
8
  Q_UNUSED(arguments)
9
9
 
10
- QString response = page()->currentFrame()->toHtml();
11
-
12
- emit finished(true, response);
10
+ QString result = page()->currentFrame()->toHtml();
11
+ emit finished(new Response(true, result));
13
12
  }
14
13
 
data/src/Url.cpp CHANGED
@@ -9,8 +9,7 @@ void Url::start(QStringList &argments) {
9
9
 
10
10
  QUrl humanUrl = page()->currentFrame()->url();
11
11
  QByteArray encodedBytes = humanUrl.toEncoded();
12
- QString response = QString(encodedBytes);
13
-
14
- emit finished(true, response);
12
+ QString urlString = QString(encodedBytes);
13
+ emit finished(new Response(true, urlString));
15
14
  }
16
15
 
data/src/Visit.cpp CHANGED
@@ -11,10 +11,10 @@ void Visit::start(QStringList &arguments) {
11
11
  }
12
12
 
13
13
  void Visit::loadFinished(bool success) {
14
- QString response;
14
+ QString message;
15
15
  if (!success)
16
- response = page()->failureString();
16
+ message = page()->failureString();
17
17
 
18
- emit finished(success, response);
18
+ emit finished(new Response(success, message));
19
19
  }
20
20
 
data/src/WebPage.cpp CHANGED
@@ -5,10 +5,15 @@
5
5
 
6
6
  WebPage::WebPage(QObject *parent) : QWebPage(parent) {
7
7
  QResource javascript(":/capybara.js");
8
- char * javascriptString = new char[javascript.size() + 1];
9
- strcpy(javascriptString, (const char *)javascript.data());
10
- javascriptString[javascript.size()] = 0;
11
- m_capybaraJavascript = javascriptString;
8
+ if (javascript.isCompressed()) {
9
+ QByteArray uncompressedBytes(qUncompress(javascript.data(), javascript.size()));
10
+ m_capybaraJavascript = QString(uncompressedBytes);
11
+ } else {
12
+ char * javascriptString = new char[javascript.size() + 1];
13
+ strcpy(javascriptString, (const char *)javascript.data());
14
+ javascriptString[javascript.size()] = 0;
15
+ m_capybaraJavascript = javascriptString;
16
+ }
12
17
  m_loading = false;
13
18
  connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
14
19
  connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
@@ -82,6 +87,6 @@ bool WebPage::isLoading() const {
82
87
  }
83
88
 
84
89
  QString WebPage::failureString() {
85
- return QString("Unable to load URL: ") + currentFrame()->url().toString();
90
+ return QString("Unable to load URL: ") + currentFrame()->requestedUrl().toString();
86
91
  }
87
92
 
data/src/capybara.js CHANGED
@@ -92,7 +92,59 @@ Capybara = {
92
92
 
93
93
  unselectOption: function(index) {
94
94
  this.nodes[index].removeAttribute("selected");
95
- }
95
+ },
96
96
 
97
+ centerPostion: function(element) {
98
+ this.reflow(element);
99
+ var rect = element.getBoundingClientRect();
100
+ var position = {
101
+ x: rect.width / 2,
102
+ y: rect.height / 2
103
+ };
104
+ do {
105
+ position.x += element.offsetLeft;
106
+ position.y += element.offsetTop;
107
+ } while ((element = element.offsetParent));
108
+ position.x = Math.floor(position.x), position.y = Math.floor(position.y);
109
+
110
+ return position;
111
+ },
112
+
113
+ reflow: function(element, force) {
114
+ if (force || element.offsetWidth === 0) {
115
+ var prop, oldStyle = {}, newStyle = {position: "absolute", visibility : "hidden", display: "block" };
116
+ for (prop in newStyle) {
117
+ oldStyle[prop] = element.style[prop];
118
+ element.style[prop] = newStyle[prop];
119
+ }
120
+ element.offsetWidth, element.offsetHeight; // force reflow
121
+ for (prop in oldStyle)
122
+ element.style[prop] = oldStyle[prop];
123
+ }
124
+ },
125
+
126
+ dragTo: function (index, targetIndex) {
127
+ var element = this.nodes[index], target = this.nodes[targetIndex];
128
+ var position = this.centerPostion(element);
129
+ var options = {
130
+ clientX: position.x,
131
+ clientY: position.y
132
+ };
133
+ var mouseTrigger = function(eventName, options) {
134
+ var eventObject = document.createEvent("MouseEvents");
135
+ eventObject.initMouseEvent(eventName, true, true, window, 0, 0, 0, options.clientX || 0, options.clientY || 0, false, false, false, false, 0, null);
136
+ element.dispatchEvent(eventObject);
137
+ }
138
+ mouseTrigger('mousedown', options);
139
+ options.clientX += 1, options.clientY += 1;
140
+ mouseTrigger('mousemove', options);
141
+
142
+ position = this.centerPostion(target), options = {
143
+ clientX: position.x,
144
+ clientY: position.y
145
+ };
146
+ mouseTrigger('mousemove', options);
147
+ mouseTrigger('mouseup', options);
148
+ }
97
149
  };
98
150
 
@@ -1,8 +1,8 @@
1
1
  TEMPLATE = app
2
2
  TARGET = webkit_server
3
3
  DESTDIR = .
4
- HEADERS = WebPage.h Server.h Connection.h Command.h Visit.h Find.h Reset.h Node.h JavascriptInvocation.h Url.h Source.h Evaluate.h Execute.h FrameFocus.h
5
- SOURCES = main.cpp WebPage.cpp Server.cpp Connection.cpp Command.cpp Visit.cpp Find.cpp Reset.cpp Node.cpp JavascriptInvocation.cpp Url.cpp Source.cpp Evaluate.cpp Execute.cpp FrameFocus.cpp
4
+ HEADERS = WebPage.h Server.h Connection.h Command.h Visit.h Find.h Reset.h Node.h JavascriptInvocation.h Url.h Source.h Evaluate.h Execute.h FrameFocus.h Response.h
5
+ SOURCES = main.cpp WebPage.cpp Server.cpp Connection.cpp Command.cpp Visit.cpp Find.cpp Reset.cpp Node.cpp JavascriptInvocation.cpp Url.cpp Source.cpp Evaluate.cpp Execute.cpp FrameFocus.cpp Response.cpp
6
6
  RESOURCES = webkit_server.qrc
7
7
  QT += network webkit
8
8
  CONFIG += console
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-webkit
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - thoughtbot
@@ -21,7 +21,7 @@ autorequire:
21
21
  bindir: bin
22
22
  cert_chain: []
23
23
 
24
- date: 2011-04-20 00:00:00 -04:00
24
+ date: 2011-05-05 00:00:00 -04:00
25
25
  default_executable:
26
26
  dependencies:
27
27
  - !ruby/object:Gem::Dependency
@@ -86,6 +86,8 @@ files:
86
86
  - src/Node.h
87
87
  - src/Reset.cpp
88
88
  - src/Reset.h
89
+ - src/Response.cpp
90
+ - src/Response.h
89
91
  - src/Server.cpp
90
92
  - src/Server.h
91
93
  - src/Source.cpp