capybara-webkit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +6 -0
  4. data/Gemfile.lock +55 -0
  5. data/LICENSE +19 -0
  6. data/README.md +37 -0
  7. data/Rakefile +76 -0
  8. data/capybara-webkit.gemspec +15 -0
  9. data/extconf.rb +2 -0
  10. data/lib/capybara-webkit.rb +7 -0
  11. data/lib/capybara/driver/webkit.rb +84 -0
  12. data/lib/capybara/driver/webkit/browser.rb +86 -0
  13. data/lib/capybara/driver/webkit/node.rb +87 -0
  14. data/spec/driver_spec.rb +465 -0
  15. data/spec/integration/driver_spec.rb +21 -0
  16. data/spec/integration/session_spec.rb +12 -0
  17. data/spec/spec_helper.rb +22 -0
  18. data/spec/support/socket_debugger.rb +42 -0
  19. data/src/Command.cpp +15 -0
  20. data/src/Command.h +28 -0
  21. data/src/Connection.cpp +130 -0
  22. data/src/Connection.h +36 -0
  23. data/src/Evaluate.cpp +84 -0
  24. data/src/Evaluate.h +22 -0
  25. data/src/Execute.cpp +17 -0
  26. data/src/Execute.h +12 -0
  27. data/src/Find.cpp +20 -0
  28. data/src/Find.h +13 -0
  29. data/src/JavascriptInvocation.cpp +14 -0
  30. data/src/JavascriptInvocation.h +19 -0
  31. data/src/Node.cpp +14 -0
  32. data/src/Node.h +13 -0
  33. data/src/Reset.cpp +16 -0
  34. data/src/Reset.h +12 -0
  35. data/src/Server.cpp +21 -0
  36. data/src/Server.h +20 -0
  37. data/src/Source.cpp +14 -0
  38. data/src/Source.h +12 -0
  39. data/src/Url.cpp +14 -0
  40. data/src/Url.h +12 -0
  41. data/src/Visit.cpp +20 -0
  42. data/src/Visit.h +16 -0
  43. data/src/WebPage.cpp +81 -0
  44. data/src/WebPage.h +29 -0
  45. data/src/capybara.js +98 -0
  46. data/src/find_command.h +13 -0
  47. data/src/main.cpp +20 -0
  48. data/src/webkit_server.pro +9 -0
  49. data/src/webkit_server.qrc +5 -0
  50. data/templates/Command.cpp +10 -0
  51. data/templates/Command.h +12 -0
  52. data/webkit_server.pro +4 -0
  53. metadata +140 -0
data/src/Execute.h ADDED
@@ -0,0 +1,12 @@
1
+ #include "Command.h"
2
+
3
+ class WebPage;
4
+
5
+ class Execute : public Command {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ Execute(WebPage *page, QObject *parent = 0);
10
+ virtual void start(QStringList &arguments);
11
+ };
12
+
data/src/Find.cpp ADDED
@@ -0,0 +1,20 @@
1
+ #include "Find.h"
2
+ #include "Command.h"
3
+ #include "WebPage.h"
4
+
5
+ Find::Find(WebPage *page, QObject *parent) : Command(page, parent) {
6
+ }
7
+
8
+ void Find::start(QStringList &arguments) {
9
+ QString response;
10
+ QVariant result = page()->invokeCapybaraFunction("find", arguments);
11
+
12
+ if (result.isValid()) {
13
+ response = result.toString();
14
+ emit finished(true, response);
15
+ } else {
16
+ response = "Invalid XPath expression";
17
+ emit finished(false, response);
18
+ }
19
+ }
20
+
data/src/Find.h ADDED
@@ -0,0 +1,13 @@
1
+ #include "Command.h"
2
+
3
+ class WebPage;
4
+
5
+ class Find : public Command {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ Find(WebPage *page, QObject *parent = 0);
10
+ virtual void start(QStringList &arguments);
11
+ };
12
+
13
+
@@ -0,0 +1,14 @@
1
+ #include "JavascriptInvocation.h"
2
+
3
+ JavascriptInvocation::JavascriptInvocation(QString &functionName, QStringList &arguments, QObject *parent) : QObject(parent) {
4
+ m_functionName = functionName;
5
+ m_arguments = arguments;
6
+ }
7
+
8
+ QString &JavascriptInvocation::functionName() {
9
+ return m_functionName;
10
+ }
11
+
12
+ QStringList &JavascriptInvocation::arguments() {
13
+ return m_arguments;
14
+ }
@@ -0,0 +1,19 @@
1
+ #include <QObject>
2
+ #include <QString>
3
+ #include <QStringList>
4
+
5
+ class JavascriptInvocation : public QObject {
6
+ Q_OBJECT
7
+ Q_PROPERTY(QString functionName READ functionName)
8
+ Q_PROPERTY(QStringList arguments READ arguments)
9
+
10
+ public:
11
+ JavascriptInvocation(QString &functionName, QStringList &arguments, QObject *parent = 0);
12
+ QString &functionName();
13
+ QStringList &arguments();
14
+
15
+ private:
16
+ QString m_functionName;
17
+ QStringList m_arguments;
18
+ };
19
+
data/src/Node.cpp ADDED
@@ -0,0 +1,14 @@
1
+ #include "Node.h"
2
+ #include "WebPage.h"
3
+
4
+ Node::Node(WebPage *page, QObject *parent) : Command(page, parent) {
5
+ }
6
+
7
+ void Node::start(QStringList &arguments) {
8
+ QStringList functionArguments(arguments);
9
+ QString functionName = functionArguments.takeFirst();
10
+ QVariant result = page()->invokeCapybaraFunction(functionName, functionArguments);
11
+ QString attributeValue = result.toString();
12
+ emit finished(true, attributeValue);
13
+ }
14
+
data/src/Node.h ADDED
@@ -0,0 +1,13 @@
1
+ #include "Command.h"
2
+ #include <QStringList>
3
+
4
+ class WebPage;
5
+
6
+ class Node : public Command {
7
+ Q_OBJECT
8
+
9
+ public:
10
+ Node(WebPage *page, QObject *parent = 0);
11
+ virtual void start(QStringList &arguments);
12
+ };
13
+
data/src/Reset.cpp ADDED
@@ -0,0 +1,16 @@
1
+ #include "Reset.h"
2
+ #include "WebPage.h"
3
+
4
+ Reset::Reset(WebPage *page, QObject *parent) : Command(page, parent) {
5
+ }
6
+
7
+ void Reset::start(QStringList &arguments) {
8
+ Q_UNUSED(arguments);
9
+
10
+ page()->triggerAction(QWebPage::Stop);
11
+ page()->mainFrame()->setHtml("<html><body></body></html>");
12
+ page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar());
13
+ QString response = "";
14
+ emit finished(true, response);
15
+ }
16
+
data/src/Reset.h ADDED
@@ -0,0 +1,12 @@
1
+ #include "Command.h"
2
+
3
+ class WebPage;
4
+
5
+ class Reset : public Command {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ Reset(WebPage *page, QObject *parent = 0);
10
+ virtual void start(QStringList &arguments);
11
+ };
12
+
data/src/Server.cpp ADDED
@@ -0,0 +1,21 @@
1
+ #include "Server.h"
2
+ #include "WebPage.h"
3
+ #include "Connection.h"
4
+
5
+ #include <QTcpServer>
6
+
7
+ Server::Server(QObject *parent) : QObject(parent) {
8
+ m_tcp_server = new QTcpServer(this);
9
+ m_page = new WebPage(this);
10
+ }
11
+
12
+ bool Server::start() {
13
+ connect(m_tcp_server, SIGNAL(newConnection()), this, SLOT(handleConnection()));
14
+ return m_tcp_server->listen(QHostAddress::Any, 9200);
15
+ }
16
+
17
+ void Server::handleConnection() {
18
+ QTcpSocket *socket = m_tcp_server->nextPendingConnection();
19
+ new Connection(socket, m_page, this);
20
+ }
21
+
data/src/Server.h ADDED
@@ -0,0 +1,20 @@
1
+ #include <QObject>
2
+
3
+ class QTcpServer;
4
+ class WebPage;
5
+
6
+ class Server : public QObject {
7
+ Q_OBJECT
8
+
9
+ public:
10
+ Server(QObject *parent = 0);
11
+ bool start();
12
+
13
+ public slots:
14
+ void handleConnection();
15
+
16
+ private:
17
+ QTcpServer *m_tcp_server;
18
+ WebPage *m_page;
19
+ };
20
+
data/src/Source.cpp ADDED
@@ -0,0 +1,14 @@
1
+ #include "Source.h"
2
+ #include "WebPage.h"
3
+
4
+ Source::Source(WebPage *page, QObject *parent) : Command(page, parent) {
5
+ }
6
+
7
+ void Source::start(QStringList &arguments) {
8
+ Q_UNUSED(arguments)
9
+
10
+ QString response = page()->mainFrame()->toHtml();
11
+
12
+ emit finished(true, response);
13
+ }
14
+
data/src/Source.h ADDED
@@ -0,0 +1,12 @@
1
+ #include "Command.h"
2
+
3
+ class WebPage;
4
+
5
+ class Source : public Command {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ Source(WebPage *page, QObject *parent = 0);
10
+ virtual void start(QStringList &arguments);
11
+ };
12
+
data/src/Url.cpp ADDED
@@ -0,0 +1,14 @@
1
+ #include "Url.h"
2
+ #include "WebPage.h"
3
+
4
+ Url::Url(WebPage *page, QObject *parent) : Command(page, parent) {
5
+ }
6
+
7
+ void Url::start(QStringList &argments) {
8
+ Q_UNUSED(argments);
9
+
10
+ QString response = page()->mainFrame()->url().toString();
11
+
12
+ emit finished(true, response);
13
+ }
14
+
data/src/Url.h ADDED
@@ -0,0 +1,12 @@
1
+ #include "Command.h"
2
+
3
+ class WebPage;
4
+
5
+ class Url : public Command {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ Url(WebPage *page, QObject *parent = 0);
10
+ virtual void start(QStringList &argments);
11
+ };
12
+
data/src/Visit.cpp ADDED
@@ -0,0 +1,20 @@
1
+ #include "Visit.h"
2
+ #include "Command.h"
3
+ #include "WebPage.h"
4
+
5
+ Visit::Visit(WebPage *page, QObject *parent) : Command(page, parent) {
6
+ connect(page, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
7
+ }
8
+
9
+ void Visit::start(QStringList &arguments) {
10
+ page()->mainFrame()->setUrl(QUrl(arguments[0]));
11
+ }
12
+
13
+ void Visit::loadFinished(bool success) {
14
+ QString response;
15
+ if (!success)
16
+ response = page()->failureString();
17
+
18
+ emit finished(success, response);
19
+ }
20
+
data/src/Visit.h ADDED
@@ -0,0 +1,16 @@
1
+ #include "Command.h"
2
+
3
+ class WebPage;
4
+
5
+ class Visit : public Command {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ Visit(WebPage *page, QObject *parent = 0);
10
+ virtual void start(QStringList &arguments);
11
+
12
+ private slots:
13
+ void loadFinished(bool success);
14
+ };
15
+
16
+
data/src/WebPage.cpp ADDED
@@ -0,0 +1,81 @@
1
+ #include "WebPage.h"
2
+ #include "JavascriptInvocation.h"
3
+ #include <QResource>
4
+ #include <iostream>
5
+
6
+ WebPage::WebPage(QObject *parent) : QWebPage(parent) {
7
+ connect(mainFrame(), SIGNAL(javaScriptWindowObjectCleared()),
8
+ this, SLOT(injectJavascriptHelpers()));
9
+ QResource javascript(":/capybara.js");
10
+ char * javascriptString = new char[javascript.size() + 1];
11
+ strcpy(javascriptString, (const char *)javascript.data());
12
+ javascriptString[javascript.size()] = 0;
13
+ m_capybaraJavascript = javascriptString;
14
+ m_loading = false;
15
+ connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
16
+ connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
17
+ }
18
+
19
+ void WebPage::injectJavascriptHelpers() {
20
+ mainFrame()->evaluateJavaScript(m_capybaraJavascript);
21
+ }
22
+
23
+ bool WebPage::shouldInterruptJavaScript() {
24
+ return false;
25
+ }
26
+
27
+ QVariant WebPage::invokeCapybaraFunction(const char *name, QStringList &arguments) {
28
+ QString qname(name);
29
+ QString objectName("CapybaraInvocation");
30
+ JavascriptInvocation invocation(qname, arguments);
31
+ mainFrame()->addToJavaScriptWindowObject(objectName, &invocation);
32
+ QString javascript = QString("Capybara.invoke()");
33
+ return mainFrame()->evaluateJavaScript(javascript);
34
+ }
35
+
36
+ QVariant WebPage::invokeCapybaraFunction(QString &name, QStringList &arguments) {
37
+ return invokeCapybaraFunction(name.toAscii().data(), arguments);
38
+ }
39
+
40
+ void WebPage::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID) {
41
+ if (!sourceID.isEmpty())
42
+ std::cout << qPrintable(sourceID) << ":" << lineNumber << " ";
43
+ std::cout << qPrintable(message) << std::endl;
44
+ }
45
+
46
+ void WebPage::javaScriptAlert(QWebFrame *frame, const QString &message) {
47
+ Q_UNUSED(frame);
48
+ std::cout << "ALERT: " << qPrintable(message) << std::endl;
49
+ }
50
+
51
+ bool WebPage::javaScriptConfirm(QWebFrame *frame, const QString &message) {
52
+ Q_UNUSED(frame);
53
+ Q_UNUSED(message);
54
+ return true;
55
+ }
56
+
57
+ bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result) {
58
+ Q_UNUSED(frame)
59
+ Q_UNUSED(message)
60
+ Q_UNUSED(defaultValue)
61
+ Q_UNUSED(result)
62
+ return false;
63
+ }
64
+
65
+ void WebPage::loadStarted() {
66
+ m_loading = true;
67
+ }
68
+
69
+ void WebPage::loadFinished(bool success) {
70
+ Q_UNUSED(success);
71
+ m_loading = false;
72
+ }
73
+
74
+ bool WebPage::isLoading() const {
75
+ return m_loading;
76
+ }
77
+
78
+ QString WebPage::failureString() {
79
+ return QString("Unable to load URL: ") + mainFrame()->url().toString();
80
+ }
81
+
data/src/WebPage.h ADDED
@@ -0,0 +1,29 @@
1
+ #include <QtWebKit>
2
+
3
+ class WebPage : public QWebPage {
4
+ Q_OBJECT
5
+
6
+ public:
7
+ WebPage(QObject *parent = 0);
8
+ QVariant invokeCapybaraFunction(const char *name, QStringList &arguments);
9
+ QVariant invokeCapybaraFunction(QString &name, QStringList &arguments);
10
+ QString failureString();
11
+
12
+ public slots:
13
+ bool shouldInterruptJavaScript();
14
+ void injectJavascriptHelpers();
15
+ void loadStarted();
16
+ void loadFinished(bool);
17
+ bool isLoading() const;
18
+
19
+ protected:
20
+ virtual void javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID);
21
+ virtual void javaScriptAlert(QWebFrame *frame, const QString &message);
22
+ virtual bool javaScriptConfirm(QWebFrame *frame, const QString &message);
23
+ virtual bool javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result);
24
+
25
+ private:
26
+ QString m_capybaraJavascript;
27
+ bool m_loading;
28
+ };
29
+
data/src/capybara.js ADDED
@@ -0,0 +1,98 @@
1
+ Capybara = {
2
+ nextIndex: 0,
3
+ nodes: {},
4
+
5
+ invoke: function () {
6
+ return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
7
+ },
8
+
9
+ find: function (xpath) {
10
+ return this.findRelativeTo(document, xpath);
11
+ },
12
+
13
+ findWithin: function (index, xpath) {
14
+ return this.findRelativeTo(this.nodes[index], xpath);
15
+ },
16
+
17
+ findRelativeTo: function (reference, xpath) {
18
+ var iterator = document.evaluate(xpath, reference, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
19
+ var node;
20
+ var results = [];
21
+ while (node = iterator.iterateNext()) {
22
+ this.nextIndex++;
23
+ this.nodes[this.nextIndex] = node;
24
+ results.push(this.nextIndex);
25
+ }
26
+ return results.join(",");
27
+ },
28
+
29
+ text: function (index) {
30
+ return this.nodes[index].innerText;
31
+ },
32
+
33
+ attribute: function (index, name) {
34
+ if (name == "checked") {
35
+ return this.nodes[index].checked;
36
+ } else {
37
+ return this.nodes[index].getAttribute(name);
38
+ }
39
+ },
40
+
41
+ tagName: function(index) {
42
+ return this.nodes[index].tagName.toLowerCase();
43
+ },
44
+
45
+ click: function (index) {
46
+ var clickEvent = document.createEvent('MouseEvents');
47
+ clickEvent.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
48
+ this.nodes[index].dispatchEvent(clickEvent);
49
+ },
50
+
51
+ trigger: function (index, eventName) {
52
+ var eventObject = document.createEvent("HTMLEvents");
53
+ eventObject.initEvent(eventName, true, true);
54
+ this.nodes[index].dispatchEvent(eventObject);
55
+ },
56
+
57
+ visible: function (index) {
58
+ var element = this.nodes[index];
59
+ while (element) {
60
+ if (element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue("display") == 'none')
61
+ return false;
62
+ element = element.parentElement;
63
+ }
64
+ return true;
65
+ },
66
+
67
+ value: function(index) {
68
+ return this.nodes[index].value;
69
+ },
70
+
71
+ set: function(index, value) {
72
+ var node = this.nodes[index];
73
+ var type = (node.type || node.tagName).toLowerCase();
74
+ if (type == "text" || type == "textarea" || type == "password") {
75
+ this.trigger(index, "focus");
76
+ node.value = value;
77
+ this.trigger(index, "keydown");
78
+ this.trigger(index, "keyup");
79
+ this.trigger(index, "change");
80
+ this.trigger(index, "blur");
81
+ } else if(type == "checkbox" || type == "radio") {
82
+ node.checked = (value == "true");
83
+ this.trigger(index, "click");
84
+ } else {
85
+ node.value = value;
86
+ }
87
+ },
88
+
89
+ selectOption: function(index) {
90
+ this.nodes[index].setAttribute("selected", "selected");
91
+ },
92
+
93
+ unselectOption: function(index) {
94
+ this.nodes[index].removeAttribute("selected");
95
+ }
96
+
97
+ };
98
+