capybara-webkit 0.1.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.
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
+