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.
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +55 -0
- data/LICENSE +19 -0
- data/README.md +37 -0
- data/Rakefile +76 -0
- data/capybara-webkit.gemspec +15 -0
- data/extconf.rb +2 -0
- data/lib/capybara-webkit.rb +7 -0
- data/lib/capybara/driver/webkit.rb +84 -0
- data/lib/capybara/driver/webkit/browser.rb +86 -0
- data/lib/capybara/driver/webkit/node.rb +87 -0
- data/spec/driver_spec.rb +465 -0
- data/spec/integration/driver_spec.rb +21 -0
- data/spec/integration/session_spec.rb +12 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/socket_debugger.rb +42 -0
- data/src/Command.cpp +15 -0
- data/src/Command.h +28 -0
- data/src/Connection.cpp +130 -0
- data/src/Connection.h +36 -0
- data/src/Evaluate.cpp +84 -0
- data/src/Evaluate.h +22 -0
- data/src/Execute.cpp +17 -0
- data/src/Execute.h +12 -0
- data/src/Find.cpp +20 -0
- data/src/Find.h +13 -0
- data/src/JavascriptInvocation.cpp +14 -0
- data/src/JavascriptInvocation.h +19 -0
- data/src/Node.cpp +14 -0
- data/src/Node.h +13 -0
- data/src/Reset.cpp +16 -0
- data/src/Reset.h +12 -0
- data/src/Server.cpp +21 -0
- data/src/Server.h +20 -0
- data/src/Source.cpp +14 -0
- data/src/Source.h +12 -0
- data/src/Url.cpp +14 -0
- data/src/Url.h +12 -0
- data/src/Visit.cpp +20 -0
- data/src/Visit.h +16 -0
- data/src/WebPage.cpp +81 -0
- data/src/WebPage.h +29 -0
- data/src/capybara.js +98 -0
- data/src/find_command.h +13 -0
- data/src/main.cpp +20 -0
- data/src/webkit_server.pro +9 -0
- data/src/webkit_server.qrc +5 -0
- data/templates/Command.cpp +10 -0
- data/templates/Command.h +12 -0
- data/webkit_server.pro +4 -0
- metadata +140 -0
data/src/Execute.h
ADDED
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,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
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
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
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
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
|
+
|