capybara-webkit 0.5.0 → 0.6.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 +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
|
}
|