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