capybara-webkit 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CONTRIBUTING.md +38 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +28 -28
- data/README.md +39 -4
- data/capybara-webkit.gemspec +2 -2
- data/lib/capybara-webkit.rb +4 -0
- data/lib/capybara/driver/webkit.rb +23 -3
- data/lib/capybara/driver/webkit/browser.rb +44 -6
- data/lib/capybara/driver/webkit/node.rb +15 -4
- data/lib/capybara/driver/webkit/socket_debugger.rb +43 -0
- data/lib/capybara_webkit_builder.rb +9 -2
- data/spec/browser_spec.rb +26 -0
- data/spec/driver_rendering_spec.rb +80 -0
- data/spec/driver_spec.rb +186 -15
- data/spec/integration/driver_spec.rb +4 -0
- data/spec/integration/session_spec.rb +2 -2
- data/src/Body.h +12 -0
- data/src/Connection.cpp +5 -2
- data/src/Header.cpp +18 -0
- data/src/Header.h +11 -0
- data/src/NetworkAccessManager.cpp +22 -0
- data/src/NetworkAccessManager.h +18 -0
- data/src/Render.cpp +19 -0
- data/src/Render.h +12 -0
- data/src/Reset.cpp +3 -0
- data/src/Server.cpp +5 -2
- data/src/Server.h +1 -0
- data/src/Source.cpp +9 -2
- data/src/Source.h +7 -0
- data/src/WebPage.cpp +79 -5
- data/src/WebPage.h +9 -0
- data/src/body.cpp +11 -0
- data/src/capybara.js +19 -4
- data/src/find_command.h +3 -0
- data/src/main.cpp +2 -1
- data/src/webkit_server.pro +2 -2
- metadata +34 -9
- data/spec/support/socket_debugger.rb +0 -42
@@ -86,6 +86,6 @@ describe Capybara::Session, "with TestApp" do
|
|
86
86
|
@session = Capybara::Session.new(:reusable_webkit, TestApp)
|
87
87
|
end
|
88
88
|
|
89
|
-
|
90
|
-
|
89
|
+
it_should_behave_like "session"
|
90
|
+
it_should_behave_like "session with javascript support"
|
91
91
|
end
|
data/src/Body.h
ADDED
data/src/Connection.cpp
CHANGED
@@ -10,6 +10,9 @@
|
|
10
10
|
#include "Evaluate.h"
|
11
11
|
#include "Execute.h"
|
12
12
|
#include "FrameFocus.h"
|
13
|
+
#include "Header.h"
|
14
|
+
#include "Render.h"
|
15
|
+
#include "Body.h"
|
13
16
|
|
14
17
|
#include <QTcpSocket>
|
15
18
|
#include <iostream>
|
@@ -55,7 +58,7 @@ void Connection::readDataBlock() {
|
|
55
58
|
buffer[m_expectingDataSize] = 0;
|
56
59
|
processNext(buffer);
|
57
60
|
m_expectingDataSize = -1;
|
58
|
-
delete buffer;
|
61
|
+
delete[] buffer;
|
59
62
|
}
|
60
63
|
|
61
64
|
void Connection::processNext(const char *data) {
|
@@ -95,7 +98,7 @@ void Connection::startCommand() {
|
|
95
98
|
SLOT(finishCommand(Response *)));
|
96
99
|
m_command->start(m_arguments);
|
97
100
|
} else {
|
98
|
-
QString failure = QString("Unknown command: ") + m_commandName + "\n";
|
101
|
+
QString failure = QString("[Capybara WebKit] Unknown command: ") + m_commandName + "\n";
|
99
102
|
writeResponse(new Response(false, failure));
|
100
103
|
}
|
101
104
|
m_commandName = QString();
|
data/src/Header.cpp
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#include "Header.h"
|
2
|
+
#include "WebPage.h"
|
3
|
+
#include "NetworkAccessManager.h"
|
4
|
+
|
5
|
+
Header::Header(WebPage *page, QObject *parent) : Command(page, parent) {
|
6
|
+
}
|
7
|
+
|
8
|
+
void Header::start(QStringList &arguments) {
|
9
|
+
QString key = arguments[0];
|
10
|
+
QString value = arguments[1];
|
11
|
+
NetworkAccessManager* networkAccessManager = qobject_cast<NetworkAccessManager*>(page()->networkAccessManager());
|
12
|
+
if (key.toLower().replace("-", "_") == "user_agent") {
|
13
|
+
page()->setUserAgent(value);
|
14
|
+
} else {
|
15
|
+
networkAccessManager->addHeader(key, value);
|
16
|
+
}
|
17
|
+
emit finished(new Response(true));
|
18
|
+
}
|
data/src/Header.h
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#include "NetworkAccessManager.h"
|
2
|
+
#include "WebPage.h"
|
3
|
+
#include <iostream>
|
4
|
+
|
5
|
+
|
6
|
+
NetworkAccessManager::NetworkAccessManager(QObject *parent):QNetworkAccessManager(parent) {
|
7
|
+
}
|
8
|
+
|
9
|
+
QNetworkReply* NetworkAccessManager::createRequest(QNetworkAccessManager::Operation oparation, const QNetworkRequest &request, QIODevice * outgoingData = 0) {
|
10
|
+
QNetworkRequest new_request(request);
|
11
|
+
QHashIterator<QString, QString> item(m_headers);
|
12
|
+
while (item.hasNext()) {
|
13
|
+
item.next();
|
14
|
+
new_request.setRawHeader(item.key().toAscii(), item.value().toAscii());
|
15
|
+
}
|
16
|
+
return QNetworkAccessManager::createRequest(oparation, new_request, outgoingData);
|
17
|
+
};
|
18
|
+
|
19
|
+
void NetworkAccessManager::addHeader(QString key, QString value) {
|
20
|
+
m_headers.insert(key, value);
|
21
|
+
};
|
22
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#include <QtNetwork/QNetworkAccessManager>
|
2
|
+
#include <QtNetwork/QNetworkRequest>
|
3
|
+
#include <QtNetwork/QNetworkReply>
|
4
|
+
|
5
|
+
class NetworkAccessManager : public QNetworkAccessManager {
|
6
|
+
|
7
|
+
Q_OBJECT
|
8
|
+
|
9
|
+
public:
|
10
|
+
NetworkAccessManager(QObject *parent = 0);
|
11
|
+
void addHeader(QString key, QString value);
|
12
|
+
|
13
|
+
protected:
|
14
|
+
QNetworkReply* createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice * outgoingData);
|
15
|
+
|
16
|
+
private:
|
17
|
+
QHash<QString, QString> m_headers;
|
18
|
+
};
|
data/src/Render.cpp
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#include "Render.h"
|
2
|
+
#include "WebPage.h"
|
3
|
+
|
4
|
+
Render::Render(WebPage *page, QObject *parent) : Command(page, parent) {
|
5
|
+
}
|
6
|
+
|
7
|
+
void Render::start(QStringList &arguments) {
|
8
|
+
QStringList functionArguments(arguments);
|
9
|
+
QString imagePath = functionArguments.takeFirst();
|
10
|
+
int width = functionArguments.takeFirst().toInt();
|
11
|
+
int height = functionArguments.takeFirst().toInt();
|
12
|
+
|
13
|
+
QSize size(width, height);
|
14
|
+
page()->setViewportSize(size);
|
15
|
+
|
16
|
+
bool result = page()->render( imagePath );
|
17
|
+
|
18
|
+
emit finished(new Response(result));
|
19
|
+
}
|
data/src/Render.h
ADDED
data/src/Reset.cpp
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#include "Reset.h"
|
2
2
|
#include "WebPage.h"
|
3
|
+
#include "NetworkAccessManager.h"
|
3
4
|
|
4
5
|
Reset::Reset(WebPage *page, QObject *parent) : Command(page, parent) {
|
5
6
|
}
|
@@ -10,6 +11,8 @@ void Reset::start(QStringList &arguments) {
|
|
10
11
|
page()->triggerAction(QWebPage::Stop);
|
11
12
|
page()->currentFrame()->setHtml("<html><body></body></html>");
|
12
13
|
page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar());
|
14
|
+
page()->setNetworkAccessManager(new NetworkAccessManager());
|
15
|
+
page()->setUserAgent(NULL);
|
13
16
|
emit finished(new Response(true));
|
14
17
|
}
|
15
18
|
|
data/src/Server.cpp
CHANGED
@@ -11,11 +11,14 @@ Server::Server(QObject *parent) : QObject(parent) {
|
|
11
11
|
|
12
12
|
bool Server::start() {
|
13
13
|
connect(m_tcp_server, SIGNAL(newConnection()), this, SLOT(handleConnection()));
|
14
|
-
return m_tcp_server->listen(QHostAddress::Any,
|
14
|
+
return m_tcp_server->listen(QHostAddress::Any, 0);
|
15
|
+
}
|
16
|
+
|
17
|
+
quint16 Server::server_port() const {
|
18
|
+
return m_tcp_server->serverPort();
|
15
19
|
}
|
16
20
|
|
17
21
|
void Server::handleConnection() {
|
18
22
|
QTcpSocket *socket = m_tcp_server->nextPendingConnection();
|
19
23
|
new Connection(socket, m_page, this);
|
20
24
|
}
|
21
|
-
|
data/src/Server.h
CHANGED
data/src/Source.cpp
CHANGED
@@ -7,7 +7,14 @@ Source::Source(WebPage *page, QObject *parent) : Command(page, parent) {
|
|
7
7
|
void Source::start(QStringList &arguments) {
|
8
8
|
Q_UNUSED(arguments)
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
QNetworkAccessManager* accessManager = page()->networkAccessManager();
|
11
|
+
QNetworkRequest request(page()->currentFrame()->url());
|
12
|
+
reply = accessManager->get(request);
|
13
|
+
|
14
|
+
connect(reply, SIGNAL(finished()), this, SLOT(sourceLoaded()));
|
15
|
+
}
|
16
|
+
|
17
|
+
void Source::sourceLoaded() {
|
18
|
+
emit finished(new Response(true, reply->readAll()));
|
12
19
|
}
|
13
20
|
|
data/src/Source.h
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#include "Command.h"
|
2
2
|
|
3
3
|
class WebPage;
|
4
|
+
class QNetworkReply;
|
4
5
|
|
5
6
|
class Source : public Command {
|
6
7
|
Q_OBJECT
|
@@ -8,5 +9,11 @@ class Source : public Command {
|
|
8
9
|
public:
|
9
10
|
Source(WebPage *page, QObject *parent = 0);
|
10
11
|
virtual void start(QStringList &arguments);
|
12
|
+
|
13
|
+
public slots:
|
14
|
+
void sourceLoaded();
|
15
|
+
|
16
|
+
private:
|
17
|
+
QNetworkReply *reply;
|
11
18
|
};
|
12
19
|
|
data/src/WebPage.cpp
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
#include "WebPage.h"
|
2
2
|
#include "JavascriptInvocation.h"
|
3
|
+
#include "NetworkAccessManager.h"
|
3
4
|
#include <QResource>
|
4
5
|
#include <iostream>
|
5
6
|
|
6
7
|
WebPage::WebPage(QObject *parent) : QWebPage(parent) {
|
8
|
+
loadJavascript();
|
9
|
+
setUserStylesheet();
|
10
|
+
|
11
|
+
m_loading = false;
|
12
|
+
|
13
|
+
this->setNetworkAccessManager(new NetworkAccessManager());
|
14
|
+
|
15
|
+
connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
|
16
|
+
connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
|
17
|
+
connect(this, SIGNAL(frameCreated(QWebFrame *)),
|
18
|
+
this, SLOT(frameCreated(QWebFrame *)));
|
19
|
+
}
|
20
|
+
|
21
|
+
void WebPage::loadJavascript() {
|
7
22
|
QResource javascript(":/capybara.js");
|
8
23
|
if (javascript.isCompressed()) {
|
9
24
|
QByteArray uncompressedBytes(qUncompress(javascript.data(), javascript.size()));
|
@@ -14,11 +29,24 @@ WebPage::WebPage(QObject *parent) : QWebPage(parent) {
|
|
14
29
|
javascriptString[javascript.size()] = 0;
|
15
30
|
m_capybaraJavascript = javascriptString;
|
16
31
|
}
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
32
|
+
}
|
33
|
+
|
34
|
+
void WebPage::setUserStylesheet() {
|
35
|
+
QString data = QString("* { font-family: 'Arial' ! important; }").toUtf8().toBase64();
|
36
|
+
QUrl url = QUrl(QString("data:text/css;charset=utf-8;base64,") + data);
|
37
|
+
settings()->setUserStyleSheetUrl(url);
|
38
|
+
}
|
39
|
+
|
40
|
+
QString WebPage::userAgentForUrl(const QUrl &url ) const {
|
41
|
+
if (!m_userAgent.isEmpty()) {
|
42
|
+
return m_userAgent;
|
43
|
+
} else {
|
44
|
+
return QWebPage::userAgentForUrl(url);
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
void WebPage::setUserAgent(QString userAgent) {
|
49
|
+
m_userAgent = userAgent;
|
22
50
|
}
|
23
51
|
|
24
52
|
void WebPage::frameCreated(QWebFrame * frame) {
|
@@ -90,3 +118,49 @@ QString WebPage::failureString() {
|
|
90
118
|
return QString("Unable to load URL: ") + currentFrame()->requestedUrl().toString();
|
91
119
|
}
|
92
120
|
|
121
|
+
bool WebPage::render(const QString &fileName) {
|
122
|
+
QFileInfo fileInfo(fileName);
|
123
|
+
QDir dir;
|
124
|
+
dir.mkpath(fileInfo.absolutePath());
|
125
|
+
|
126
|
+
QSize viewportSize = this->viewportSize();
|
127
|
+
QSize pageSize = this->mainFrame()->contentsSize();
|
128
|
+
if (pageSize.isEmpty()) {
|
129
|
+
return false;
|
130
|
+
}
|
131
|
+
|
132
|
+
QImage buffer(pageSize, QImage::Format_ARGB32);
|
133
|
+
buffer.fill(qRgba(255, 255, 255, 0));
|
134
|
+
|
135
|
+
QPainter p(&buffer);
|
136
|
+
p.setRenderHint( QPainter::Antialiasing, true);
|
137
|
+
p.setRenderHint( QPainter::TextAntialiasing, true);
|
138
|
+
p.setRenderHint( QPainter::SmoothPixmapTransform, true);
|
139
|
+
|
140
|
+
this->setViewportSize(pageSize);
|
141
|
+
this->mainFrame()->render(&p);
|
142
|
+
p.end();
|
143
|
+
this->setViewportSize(viewportSize);
|
144
|
+
|
145
|
+
return buffer.save(fileName);
|
146
|
+
}
|
147
|
+
|
148
|
+
QString WebPage::chooseFile(QWebFrame *parentFrame, const QString &suggestedFile) {
|
149
|
+
Q_UNUSED(parentFrame);
|
150
|
+
Q_UNUSED(suggestedFile);
|
151
|
+
|
152
|
+
return getLastAttachedFileName();
|
153
|
+
}
|
154
|
+
|
155
|
+
bool WebPage::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) {
|
156
|
+
if (extension == ChooseMultipleFilesExtension) {
|
157
|
+
QStringList names = QStringList() << getLastAttachedFileName();
|
158
|
+
static_cast<ChooseMultipleFilesExtensionReturn*>(output)->fileNames = names;
|
159
|
+
return true;
|
160
|
+
}
|
161
|
+
return false;
|
162
|
+
}
|
163
|
+
|
164
|
+
QString WebPage::getLastAttachedFileName() {
|
165
|
+
return currentFrame()->evaluateJavaScript(QString("Capybara.lastAttachedFile")).toString();
|
166
|
+
}
|
data/src/WebPage.h
CHANGED
@@ -8,6 +8,10 @@ class WebPage : public QWebPage {
|
|
8
8
|
QVariant invokeCapybaraFunction(const char *name, QStringList &arguments);
|
9
9
|
QVariant invokeCapybaraFunction(QString &name, QStringList &arguments);
|
10
10
|
QString failureString();
|
11
|
+
QString userAgentForUrl(const QUrl &url ) const;
|
12
|
+
void setUserAgent(QString userAgent);
|
13
|
+
bool render(const QString &fileName);
|
14
|
+
virtual bool extension (Extension extension, const ExtensionOption *option=0, ExtensionReturn *output=0);
|
11
15
|
|
12
16
|
public slots:
|
13
17
|
bool shouldInterruptJavaScript();
|
@@ -22,9 +26,14 @@ class WebPage : public QWebPage {
|
|
22
26
|
virtual void javaScriptAlert(QWebFrame *frame, const QString &message);
|
23
27
|
virtual bool javaScriptConfirm(QWebFrame *frame, const QString &message);
|
24
28
|
virtual bool javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result);
|
29
|
+
virtual QString chooseFile(QWebFrame * parentFrame, const QString &suggestedFile);
|
25
30
|
|
26
31
|
private:
|
27
32
|
QString m_capybaraJavascript;
|
33
|
+
QString m_userAgent;
|
28
34
|
bool m_loading;
|
35
|
+
QString getLastAttachedFileName();
|
36
|
+
void loadJavascript();
|
37
|
+
void setUserStylesheet();
|
29
38
|
};
|
30
39
|
|
data/src/body.cpp
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#include "Body.h"
|
2
|
+
#include "WebPage.h"
|
3
|
+
|
4
|
+
Body::Body(WebPage *page, QObject *parent) : Command(page, parent) {
|
5
|
+
}
|
6
|
+
|
7
|
+
void Body::start(QStringList &arguments) {
|
8
|
+
Q_UNUSED(arguments);
|
9
|
+
QString result = page()->currentFrame()->toHtml();
|
10
|
+
emit finished(new Response(true, result));
|
11
|
+
}
|
data/src/capybara.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
Capybara = {
|
2
2
|
nextIndex: 0,
|
3
3
|
nodes: {},
|
4
|
+
lastAttachedFile: "",
|
4
5
|
|
5
6
|
invoke: function () {
|
6
7
|
return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
|
@@ -27,16 +28,22 @@ Capybara = {
|
|
27
28
|
},
|
28
29
|
|
29
30
|
text: function (index) {
|
30
|
-
|
31
|
+
var node = this.nodes[index];
|
32
|
+
var type = (node.type || node.tagName).toLowerCase();
|
33
|
+
if (type == "textarea") {
|
34
|
+
return node.innerHTML;
|
35
|
+
} else {
|
36
|
+
return node.innerText;
|
37
|
+
}
|
31
38
|
},
|
32
39
|
|
33
40
|
attribute: function (index, name) {
|
34
41
|
switch(name) {
|
35
|
-
case 'checked':
|
42
|
+
case 'checked':
|
36
43
|
return this.nodes[index].checked;
|
37
44
|
break;
|
38
45
|
|
39
|
-
case 'disabled':
|
46
|
+
case 'disabled':
|
40
47
|
return this.nodes[index].disabled;
|
41
48
|
break;
|
42
49
|
|
@@ -84,6 +91,10 @@ Capybara = {
|
|
84
91
|
return true;
|
85
92
|
},
|
86
93
|
|
94
|
+
selected: function (index) {
|
95
|
+
return this.nodes[index].selected;
|
96
|
+
},
|
97
|
+
|
87
98
|
value: function(index) {
|
88
99
|
return this.nodes[index].value;
|
89
100
|
},
|
@@ -94,7 +105,8 @@ Capybara = {
|
|
94
105
|
if (type == "text" || type == "textarea" || type == "password") {
|
95
106
|
this.trigger(index, "focus");
|
96
107
|
node.value = "";
|
97
|
-
|
108
|
+
var length = this.attribute(index, "maxlength") || value.length;
|
109
|
+
for(var strindex = 0; strindex < length; strindex++) {
|
98
110
|
node.value += value[strindex];
|
99
111
|
this.trigger(index, "keydown");
|
100
112
|
this.keypress(index, false, false, false, false, 0, value[strindex]);
|
@@ -106,6 +118,9 @@ Capybara = {
|
|
106
118
|
node.checked = (value == "true");
|
107
119
|
this.trigger(index, "click");
|
108
120
|
this.trigger(index, "change");
|
121
|
+
} else if(type == "file") {
|
122
|
+
this.lastAttachedFile = value;
|
123
|
+
this.trigger(index, "click");
|
109
124
|
} else {
|
110
125
|
node.value = value;
|
111
126
|
}
|