capybara-webkit 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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