imseng-capybara-webkit 0.12.1
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 +21 -0
- data/.rspec +2 -0
- data/Appraisals +7 -0
- data/CONTRIBUTING.md +47 -0
- data/ChangeLog +70 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/NEWS.md +36 -0
- data/README.md +114 -0
- data/Rakefile +65 -0
- data/bin/Info.plist +22 -0
- data/capybara-webkit.gemspec +28 -0
- data/extconf.rb +2 -0
- data/gemfiles/1.0.gemfile +7 -0
- data/gemfiles/1.0.gemfile.lock +70 -0
- data/gemfiles/1.1.gemfile +7 -0
- data/gemfiles/1.1.gemfile.lock +70 -0
- data/lib/capybara/driver/webkit/browser.rb +164 -0
- data/lib/capybara/driver/webkit/connection.rb +120 -0
- data/lib/capybara/driver/webkit/cookie_jar.rb +55 -0
- data/lib/capybara/driver/webkit/node.rb +118 -0
- data/lib/capybara/driver/webkit/socket_debugger.rb +43 -0
- data/lib/capybara/driver/webkit/version.rb +7 -0
- data/lib/capybara/driver/webkit.rb +136 -0
- data/lib/capybara/webkit/matchers.rb +37 -0
- data/lib/capybara/webkit.rb +13 -0
- data/lib/capybara-webkit.rb +1 -0
- data/lib/capybara_webkit_builder.rb +68 -0
- data/spec/browser_spec.rb +173 -0
- data/spec/capybara_webkit_builder_spec.rb +37 -0
- data/spec/connection_spec.rb +54 -0
- data/spec/cookie_jar_spec.rb +48 -0
- data/spec/driver_rendering_spec.rb +80 -0
- data/spec/driver_resize_window_spec.rb +59 -0
- data/spec/driver_spec.rb +1552 -0
- data/spec/integration/driver_spec.rb +20 -0
- data/spec/integration/session_spec.rb +137 -0
- data/spec/self_signed_ssl_cert.rb +42 -0
- data/spec/spec_helper.rb +46 -0
- data/src/Body.h +12 -0
- data/src/ClearCookies.cpp +15 -0
- data/src/ClearCookies.h +11 -0
- data/src/Command.cpp +19 -0
- data/src/Command.h +31 -0
- data/src/CommandFactory.cpp +37 -0
- data/src/CommandFactory.h +16 -0
- data/src/CommandParser.cpp +76 -0
- data/src/CommandParser.h +33 -0
- data/src/Connection.cpp +71 -0
- data/src/Connection.h +37 -0
- data/src/ConsoleMessages.cpp +10 -0
- data/src/ConsoleMessages.h +12 -0
- data/src/CurrentUrl.cpp +68 -0
- data/src/CurrentUrl.h +16 -0
- data/src/Evaluate.cpp +84 -0
- data/src/Evaluate.h +22 -0
- data/src/Execute.cpp +16 -0
- data/src/Execute.h +12 -0
- data/src/Find.cpp +19 -0
- data/src/Find.h +13 -0
- data/src/FrameFocus.cpp +66 -0
- data/src/FrameFocus.h +28 -0
- data/src/GetCookies.cpp +20 -0
- data/src/GetCookies.h +14 -0
- data/src/Header.cpp +18 -0
- data/src/Header.h +11 -0
- data/src/Headers.cpp +10 -0
- data/src/Headers.h +12 -0
- data/src/IgnoreSslErrors.cpp +12 -0
- data/src/IgnoreSslErrors.h +12 -0
- data/src/JavascriptInvocation.cpp +14 -0
- data/src/JavascriptInvocation.h +19 -0
- data/src/NetworkAccessManager.cpp +29 -0
- data/src/NetworkAccessManager.h +19 -0
- data/src/NetworkCookieJar.cpp +101 -0
- data/src/NetworkCookieJar.h +15 -0
- data/src/Node.cpp +14 -0
- data/src/Node.h +13 -0
- data/src/NullCommand.cpp +10 -0
- data/src/NullCommand.h +11 -0
- data/src/PageLoadingCommand.cpp +46 -0
- data/src/PageLoadingCommand.h +40 -0
- data/src/Render.cpp +18 -0
- data/src/Render.h +12 -0
- data/src/RequestedUrl.cpp +12 -0
- data/src/RequestedUrl.h +12 -0
- data/src/Reset.cpp +29 -0
- data/src/Reset.h +15 -0
- data/src/ResizeWindow.cpp +16 -0
- data/src/ResizeWindow.h +12 -0
- data/src/Response.cpp +24 -0
- data/src/Response.h +15 -0
- data/src/Server.cpp +24 -0
- data/src/Server.h +21 -0
- data/src/SetCookie.cpp +16 -0
- data/src/SetCookie.h +11 -0
- data/src/SetProxy.cpp +22 -0
- data/src/SetProxy.h +11 -0
- data/src/Source.cpp +18 -0
- data/src/Source.h +19 -0
- data/src/Status.cpp +12 -0
- data/src/Status.h +12 -0
- data/src/UnsupportedContentHandler.cpp +32 -0
- data/src/UnsupportedContentHandler.h +18 -0
- data/src/Url.cpp +12 -0
- data/src/Url.h +12 -0
- data/src/Visit.cpp +12 -0
- data/src/Visit.h +12 -0
- data/src/WebPage.cpp +239 -0
- data/src/WebPage.h +58 -0
- data/src/body.cpp +10 -0
- data/src/capybara.js +315 -0
- data/src/find_command.h +29 -0
- data/src/main.cpp +33 -0
- data/src/webkit_server.pro +85 -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 +298 -0
data/src/WebPage.cpp
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
#include "WebPage.h"
|
2
|
+
#include "JavascriptInvocation.h"
|
3
|
+
#include "NetworkAccessManager.h"
|
4
|
+
#include "NetworkCookieJar.h"
|
5
|
+
#include "UnsupportedContentHandler.h"
|
6
|
+
#include <QResource>
|
7
|
+
#include <iostream>
|
8
|
+
|
9
|
+
WebPage::WebPage(QObject *parent) : QWebPage(parent) {
|
10
|
+
setForwardUnsupportedContent(true);
|
11
|
+
loadJavascript();
|
12
|
+
setUserStylesheet();
|
13
|
+
|
14
|
+
m_loading = false;
|
15
|
+
m_ignoreSslErrors = false;
|
16
|
+
this->setCustomNetworkAccessManager();
|
17
|
+
|
18
|
+
connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
|
19
|
+
connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
|
20
|
+
connect(this, SIGNAL(frameCreated(QWebFrame *)),
|
21
|
+
this, SLOT(frameCreated(QWebFrame *)));
|
22
|
+
connect(this, SIGNAL(unsupportedContent(QNetworkReply*)),
|
23
|
+
this, SLOT(handleUnsupportedContent(QNetworkReply*)));
|
24
|
+
resetWindowSize();
|
25
|
+
}
|
26
|
+
|
27
|
+
void WebPage::resetWindowSize() {
|
28
|
+
this->setViewportSize(QSize(1680, 1050));
|
29
|
+
this->settings()->setAttribute(QWebSettings::LocalStorageDatabaseEnabled, true);
|
30
|
+
}
|
31
|
+
|
32
|
+
void WebPage::setCustomNetworkAccessManager() {
|
33
|
+
NetworkAccessManager *manager = new NetworkAccessManager();
|
34
|
+
manager->setCookieJar(new NetworkCookieJar());
|
35
|
+
this->setNetworkAccessManager(manager);
|
36
|
+
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *)));
|
37
|
+
connect(manager, SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
|
38
|
+
this, SLOT(handleSslErrorsForReply(QNetworkReply *, QList<QSslError>)));
|
39
|
+
}
|
40
|
+
|
41
|
+
void WebPage::loadJavascript() {
|
42
|
+
QResource javascript(":/capybara.js");
|
43
|
+
if (javascript.isCompressed()) {
|
44
|
+
QByteArray uncompressedBytes(qUncompress(javascript.data(), javascript.size()));
|
45
|
+
m_capybaraJavascript = QString(uncompressedBytes);
|
46
|
+
} else {
|
47
|
+
char * javascriptString = new char[javascript.size() + 1];
|
48
|
+
strcpy(javascriptString, (const char *)javascript.data());
|
49
|
+
javascriptString[javascript.size()] = 0;
|
50
|
+
m_capybaraJavascript = javascriptString;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
void WebPage::setUserStylesheet() {
|
55
|
+
QString data = QString("* { font-family: 'Arial' ! important; }").toUtf8().toBase64();
|
56
|
+
QUrl url = QUrl(QString("data:text/css;charset=utf-8;base64,") + data);
|
57
|
+
settings()->setUserStyleSheetUrl(url);
|
58
|
+
}
|
59
|
+
|
60
|
+
QString WebPage::userAgentForUrl(const QUrl &url ) const {
|
61
|
+
if (!m_userAgent.isEmpty()) {
|
62
|
+
return m_userAgent;
|
63
|
+
} else {
|
64
|
+
return QWebPage::userAgentForUrl(url);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
QString WebPage::consoleMessages() {
|
69
|
+
return m_consoleMessages.join("\n");
|
70
|
+
}
|
71
|
+
|
72
|
+
void WebPage::setUserAgent(QString userAgent) {
|
73
|
+
m_userAgent = userAgent;
|
74
|
+
}
|
75
|
+
|
76
|
+
void WebPage::frameCreated(QWebFrame * frame) {
|
77
|
+
connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
|
78
|
+
this, SLOT(injectJavascriptHelpers()));
|
79
|
+
}
|
80
|
+
|
81
|
+
void WebPage::injectJavascriptHelpers() {
|
82
|
+
QWebFrame* frame = qobject_cast<QWebFrame *>(QObject::sender());
|
83
|
+
frame->evaluateJavaScript(m_capybaraJavascript);
|
84
|
+
}
|
85
|
+
|
86
|
+
bool WebPage::shouldInterruptJavaScript() {
|
87
|
+
return false;
|
88
|
+
}
|
89
|
+
|
90
|
+
QVariant WebPage::invokeCapybaraFunction(const char *name, QStringList &arguments) {
|
91
|
+
QString qname(name);
|
92
|
+
QString objectName("CapybaraInvocation");
|
93
|
+
JavascriptInvocation invocation(qname, arguments);
|
94
|
+
currentFrame()->addToJavaScriptWindowObject(objectName, &invocation);
|
95
|
+
QString javascript = QString("Capybara.invoke()");
|
96
|
+
return currentFrame()->evaluateJavaScript(javascript);
|
97
|
+
}
|
98
|
+
|
99
|
+
QVariant WebPage::invokeCapybaraFunction(QString &name, QStringList &arguments) {
|
100
|
+
return invokeCapybaraFunction(name.toAscii().data(), arguments);
|
101
|
+
}
|
102
|
+
|
103
|
+
void WebPage::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID) {
|
104
|
+
QString fullMessage = QString::number(lineNumber) + "|" + message;
|
105
|
+
if (!sourceID.isEmpty())
|
106
|
+
fullMessage = sourceID + "|" + fullMessage;
|
107
|
+
m_consoleMessages.append(fullMessage);
|
108
|
+
std::cout << qPrintable(fullMessage) << std::endl;
|
109
|
+
}
|
110
|
+
|
111
|
+
void WebPage::javaScriptAlert(QWebFrame *frame, const QString &message) {
|
112
|
+
Q_UNUSED(frame);
|
113
|
+
std::cout << "ALERT: " << qPrintable(message) << std::endl;
|
114
|
+
}
|
115
|
+
|
116
|
+
bool WebPage::javaScriptConfirm(QWebFrame *frame, const QString &message) {
|
117
|
+
Q_UNUSED(frame);
|
118
|
+
Q_UNUSED(message);
|
119
|
+
return true;
|
120
|
+
}
|
121
|
+
|
122
|
+
bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result) {
|
123
|
+
Q_UNUSED(frame)
|
124
|
+
Q_UNUSED(message)
|
125
|
+
Q_UNUSED(defaultValue)
|
126
|
+
Q_UNUSED(result)
|
127
|
+
return false;
|
128
|
+
}
|
129
|
+
|
130
|
+
void WebPage::loadStarted() {
|
131
|
+
m_loading = true;
|
132
|
+
}
|
133
|
+
|
134
|
+
void WebPage::loadFinished(bool success) {
|
135
|
+
m_loading = false;
|
136
|
+
emit pageFinished(success);
|
137
|
+
}
|
138
|
+
|
139
|
+
bool WebPage::isLoading() const {
|
140
|
+
return m_loading;
|
141
|
+
}
|
142
|
+
|
143
|
+
QString WebPage::failureString() {
|
144
|
+
return QString("Unable to load URL: ") + currentFrame()->requestedUrl().toString();
|
145
|
+
}
|
146
|
+
|
147
|
+
bool WebPage::render(const QString &fileName) {
|
148
|
+
QFileInfo fileInfo(fileName);
|
149
|
+
QDir dir;
|
150
|
+
dir.mkpath(fileInfo.absolutePath());
|
151
|
+
|
152
|
+
QSize viewportSize = this->viewportSize();
|
153
|
+
QSize pageSize = this->mainFrame()->contentsSize();
|
154
|
+
if (pageSize.isEmpty()) {
|
155
|
+
return false;
|
156
|
+
}
|
157
|
+
|
158
|
+
QImage buffer(pageSize, QImage::Format_ARGB32);
|
159
|
+
buffer.fill(qRgba(255, 255, 255, 0));
|
160
|
+
|
161
|
+
QPainter p(&buffer);
|
162
|
+
p.setRenderHint( QPainter::Antialiasing, true);
|
163
|
+
p.setRenderHint( QPainter::TextAntialiasing, true);
|
164
|
+
p.setRenderHint( QPainter::SmoothPixmapTransform, true);
|
165
|
+
|
166
|
+
this->setViewportSize(pageSize);
|
167
|
+
this->mainFrame()->render(&p);
|
168
|
+
p.end();
|
169
|
+
this->setViewportSize(viewportSize);
|
170
|
+
|
171
|
+
return buffer.save(fileName);
|
172
|
+
}
|
173
|
+
|
174
|
+
QString WebPage::chooseFile(QWebFrame *parentFrame, const QString &suggestedFile) {
|
175
|
+
Q_UNUSED(parentFrame);
|
176
|
+
Q_UNUSED(suggestedFile);
|
177
|
+
|
178
|
+
return getLastAttachedFileName();
|
179
|
+
}
|
180
|
+
|
181
|
+
bool WebPage::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) {
|
182
|
+
Q_UNUSED(option);
|
183
|
+
if (extension == ChooseMultipleFilesExtension) {
|
184
|
+
QStringList names = QStringList() << getLastAttachedFileName();
|
185
|
+
static_cast<ChooseMultipleFilesExtensionReturn*>(output)->fileNames = names;
|
186
|
+
return true;
|
187
|
+
}
|
188
|
+
return false;
|
189
|
+
}
|
190
|
+
|
191
|
+
QString WebPage::getLastAttachedFileName() {
|
192
|
+
return currentFrame()->evaluateJavaScript(QString("Capybara.lastAttachedFile")).toString();
|
193
|
+
}
|
194
|
+
|
195
|
+
void WebPage::replyFinished(QNetworkReply *reply) {
|
196
|
+
if (reply->url() == this->currentFrame()->url()) {
|
197
|
+
QStringList headers;
|
198
|
+
m_lastStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
199
|
+
QList<QByteArray> list = reply->rawHeaderList();
|
200
|
+
|
201
|
+
int length = list.size();
|
202
|
+
for(int i = 0; i < length; i++) {
|
203
|
+
headers << list.at(i)+": "+reply->rawHeader(list.at(i));
|
204
|
+
}
|
205
|
+
|
206
|
+
m_pageHeaders = headers.join("\n");
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
void WebPage::handleSslErrorsForReply(QNetworkReply *reply, const QList<QSslError> &errors) {
|
211
|
+
if (m_ignoreSslErrors)
|
212
|
+
reply->ignoreSslErrors(errors);
|
213
|
+
}
|
214
|
+
|
215
|
+
void WebPage::ignoreSslErrors() {
|
216
|
+
m_ignoreSslErrors = true;
|
217
|
+
}
|
218
|
+
|
219
|
+
int WebPage::getLastStatus() {
|
220
|
+
return m_lastStatus;
|
221
|
+
}
|
222
|
+
|
223
|
+
void WebPage::resetResponseHeaders() {
|
224
|
+
m_lastStatus = 0;
|
225
|
+
m_pageHeaders = QString();
|
226
|
+
}
|
227
|
+
|
228
|
+
void WebPage::resetConsoleMessages() {
|
229
|
+
m_consoleMessages.clear();
|
230
|
+
}
|
231
|
+
|
232
|
+
QString WebPage::pageHeaders() {
|
233
|
+
return m_pageHeaders;
|
234
|
+
}
|
235
|
+
|
236
|
+
void WebPage::handleUnsupportedContent(QNetworkReply *reply) {
|
237
|
+
UnsupportedContentHandler *handler = new UnsupportedContentHandler(this, reply);
|
238
|
+
Q_UNUSED(handler);
|
239
|
+
}
|
data/src/WebPage.h
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#include <QtWebKit>
|
2
|
+
#include <QtNetwork>
|
3
|
+
|
4
|
+
class WebPage : public QWebPage {
|
5
|
+
Q_OBJECT
|
6
|
+
|
7
|
+
public:
|
8
|
+
WebPage(QObject *parent = 0);
|
9
|
+
QVariant invokeCapybaraFunction(const char *name, QStringList &arguments);
|
10
|
+
QVariant invokeCapybaraFunction(QString &name, QStringList &arguments);
|
11
|
+
QString failureString();
|
12
|
+
QString userAgentForUrl(const QUrl &url ) const;
|
13
|
+
void setUserAgent(QString userAgent);
|
14
|
+
int getLastStatus();
|
15
|
+
void resetResponseHeaders();
|
16
|
+
void setCustomNetworkAccessManager();
|
17
|
+
bool render(const QString &fileName);
|
18
|
+
virtual bool extension (Extension extension, const ExtensionOption *option=0, ExtensionReturn *output=0);
|
19
|
+
void ignoreSslErrors();
|
20
|
+
QString consoleMessages();
|
21
|
+
void resetConsoleMessages();
|
22
|
+
void resetWindowSize();
|
23
|
+
|
24
|
+
public slots:
|
25
|
+
bool shouldInterruptJavaScript();
|
26
|
+
void injectJavascriptHelpers();
|
27
|
+
void loadStarted();
|
28
|
+
void loadFinished(bool);
|
29
|
+
bool isLoading() const;
|
30
|
+
QString pageHeaders();
|
31
|
+
void frameCreated(QWebFrame *);
|
32
|
+
void replyFinished(QNetworkReply *reply);
|
33
|
+
void handleSslErrorsForReply(QNetworkReply *reply, const QList<QSslError> &);
|
34
|
+
void handleUnsupportedContent(QNetworkReply *reply);
|
35
|
+
|
36
|
+
signals:
|
37
|
+
void pageFinished(bool);
|
38
|
+
|
39
|
+
protected:
|
40
|
+
virtual void javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID);
|
41
|
+
virtual void javaScriptAlert(QWebFrame *frame, const QString &message);
|
42
|
+
virtual bool javaScriptConfirm(QWebFrame *frame, const QString &message);
|
43
|
+
virtual bool javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result);
|
44
|
+
virtual QString chooseFile(QWebFrame * parentFrame, const QString &suggestedFile);
|
45
|
+
|
46
|
+
private:
|
47
|
+
QString m_capybaraJavascript;
|
48
|
+
QString m_userAgent;
|
49
|
+
bool m_loading;
|
50
|
+
QString getLastAttachedFileName();
|
51
|
+
void loadJavascript();
|
52
|
+
void setUserStylesheet();
|
53
|
+
int m_lastStatus;
|
54
|
+
QString m_pageHeaders;
|
55
|
+
bool m_ignoreSslErrors;
|
56
|
+
QStringList m_consoleMessages;
|
57
|
+
};
|
58
|
+
|
data/src/body.cpp
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#include "Body.h"
|
2
|
+
#include "WebPage.h"
|
3
|
+
|
4
|
+
Body::Body(WebPage *page, QStringList &arguments, QObject *parent) : Command(page, arguments, parent) {
|
5
|
+
}
|
6
|
+
|
7
|
+
void Body::start() {
|
8
|
+
QString result = page()->currentFrame()->toHtml();
|
9
|
+
emit finished(new Response(true, result));
|
10
|
+
}
|
data/src/capybara.js
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
Capybara = {
|
2
|
+
nextIndex: 0,
|
3
|
+
nodes: {},
|
4
|
+
lastAttachedFile: "",
|
5
|
+
|
6
|
+
invoke: function () {
|
7
|
+
return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
|
8
|
+
},
|
9
|
+
|
10
|
+
find: function (xpath) {
|
11
|
+
return this.findRelativeTo(document, xpath);
|
12
|
+
},
|
13
|
+
|
14
|
+
findWithin: function (index, xpath) {
|
15
|
+
return this.findRelativeTo(this.nodes[index], xpath);
|
16
|
+
},
|
17
|
+
|
18
|
+
findRelativeTo: function (reference, xpath) {
|
19
|
+
var iterator = document.evaluate(xpath, reference, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
20
|
+
var node;
|
21
|
+
var results = [];
|
22
|
+
while (node = iterator.iterateNext()) {
|
23
|
+
this.nextIndex++;
|
24
|
+
this.nodes[this.nextIndex] = node;
|
25
|
+
results.push(this.nextIndex);
|
26
|
+
}
|
27
|
+
return results.join(",");
|
28
|
+
},
|
29
|
+
|
30
|
+
isAttached: function(index) {
|
31
|
+
return document.evaluate("ancestor-or-self::html", this.nodes[index], null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue != null;
|
32
|
+
},
|
33
|
+
|
34
|
+
text: function (index) {
|
35
|
+
var node = this.nodes[index];
|
36
|
+
var type = (node.type || node.tagName).toLowerCase();
|
37
|
+
if (type == "textarea") {
|
38
|
+
return node.innerHTML;
|
39
|
+
} else {
|
40
|
+
return node.innerText;
|
41
|
+
}
|
42
|
+
},
|
43
|
+
|
44
|
+
attribute: function (index, name) {
|
45
|
+
switch(name) {
|
46
|
+
case 'checked':
|
47
|
+
return this.nodes[index].checked;
|
48
|
+
break;
|
49
|
+
|
50
|
+
case 'disabled':
|
51
|
+
return this.nodes[index].disabled;
|
52
|
+
break;
|
53
|
+
|
54
|
+
default:
|
55
|
+
return this.nodes[index].getAttribute(name);
|
56
|
+
}
|
57
|
+
},
|
58
|
+
|
59
|
+
path: function(index) {
|
60
|
+
return "/" + this.getXPathNode(this.nodes[index]).join("/");
|
61
|
+
},
|
62
|
+
|
63
|
+
getXPathNode: function(node, path) {
|
64
|
+
path = path || [];
|
65
|
+
if (node.parentNode) {
|
66
|
+
path = this.getXPathNode(node.parentNode, path);
|
67
|
+
}
|
68
|
+
|
69
|
+
var first = node;
|
70
|
+
while (first.previousSibling)
|
71
|
+
first = first.previousSibling;
|
72
|
+
|
73
|
+
var count = 0;
|
74
|
+
var index = 0;
|
75
|
+
var iter = first;
|
76
|
+
while (iter) {
|
77
|
+
if (iter.nodeType == 1 && iter.nodeName == node.nodeName)
|
78
|
+
count++;
|
79
|
+
if (iter.isSameNode(node))
|
80
|
+
index = count;
|
81
|
+
iter = iter.nextSibling;
|
82
|
+
continue;
|
83
|
+
}
|
84
|
+
|
85
|
+
if (node.nodeType == 1)
|
86
|
+
path.push(node.nodeName.toLowerCase() + (node.id ? "[@id='"+node.id+"']" : count > 1 ? "["+index+"]" : ''));
|
87
|
+
|
88
|
+
return path;
|
89
|
+
},
|
90
|
+
|
91
|
+
tagName: function(index) {
|
92
|
+
return this.nodes[index].tagName.toLowerCase();
|
93
|
+
},
|
94
|
+
|
95
|
+
submit: function(index) {
|
96
|
+
return this.nodes[index].submit();
|
97
|
+
},
|
98
|
+
|
99
|
+
mousedown: function(index) {
|
100
|
+
var mousedownEvent = document.createEvent('MouseEvents');
|
101
|
+
mousedownEvent.initMouseEvent('mousedown', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
102
|
+
this.nodes[index].dispatchEvent(mousedownEvent);
|
103
|
+
},
|
104
|
+
|
105
|
+
mouseup: function(index) {
|
106
|
+
var mouseupEvent = document.createEvent('MouseEvents');
|
107
|
+
mouseupEvent.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
108
|
+
this.nodes[index].dispatchEvent(mouseupEvent);
|
109
|
+
},
|
110
|
+
|
111
|
+
click: function (index) {
|
112
|
+
this.mousedown(index);
|
113
|
+
this.mouseup(index);
|
114
|
+
var clickEvent = document.createEvent('MouseEvents');
|
115
|
+
clickEvent.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
116
|
+
this.nodes[index].dispatchEvent(clickEvent);
|
117
|
+
},
|
118
|
+
|
119
|
+
trigger: function (index, eventName) {
|
120
|
+
var eventObject = document.createEvent("HTMLEvents");
|
121
|
+
eventObject.initEvent(eventName, true, true);
|
122
|
+
this.nodes[index].dispatchEvent(eventObject);
|
123
|
+
},
|
124
|
+
|
125
|
+
keypress: function(index, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
|
126
|
+
var eventObject = document.createEvent("Events");
|
127
|
+
eventObject.initEvent('keypress', true, true);
|
128
|
+
eventObject.window = window;
|
129
|
+
eventObject.altKey = altKey;
|
130
|
+
eventObject.ctrlKey = ctrlKey;
|
131
|
+
eventObject.shiftKey = shiftKey;
|
132
|
+
eventObject.metaKey = metaKey;
|
133
|
+
eventObject.keyCode = keyCode;
|
134
|
+
eventObject.charCode = charCode;
|
135
|
+
eventObject.which = keyCode;
|
136
|
+
this.nodes[index].dispatchEvent(eventObject);
|
137
|
+
},
|
138
|
+
|
139
|
+
keyupdown: function(index, eventName, keyCode) {
|
140
|
+
var eventObject = document.createEvent("HTMLEvents");
|
141
|
+
eventObject.initEvent(eventName, true, true);
|
142
|
+
eventObject.keyCode = keyCode;
|
143
|
+
eventObject.which = keyCode;
|
144
|
+
eventObject.charCode = 0;
|
145
|
+
this.nodes[index].dispatchEvent(eventObject);
|
146
|
+
},
|
147
|
+
|
148
|
+
visible: function (index) {
|
149
|
+
var element = this.nodes[index];
|
150
|
+
while (element) {
|
151
|
+
if (element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue("display") == 'none')
|
152
|
+
return false;
|
153
|
+
element = element.parentElement;
|
154
|
+
}
|
155
|
+
return true;
|
156
|
+
},
|
157
|
+
|
158
|
+
selected: function (index) {
|
159
|
+
return this.nodes[index].selected;
|
160
|
+
},
|
161
|
+
|
162
|
+
value: function(index) {
|
163
|
+
return this.nodes[index].value;
|
164
|
+
},
|
165
|
+
|
166
|
+
characterToKeyCode: function(character) {
|
167
|
+
var code = character.toUpperCase().charCodeAt(0);
|
168
|
+
var specialKeys = {
|
169
|
+
96: 192, //`
|
170
|
+
45: 189, //-
|
171
|
+
61: 187, //=
|
172
|
+
91: 219, //[
|
173
|
+
93: 221, //]
|
174
|
+
92: 220, //\
|
175
|
+
59: 186, //;
|
176
|
+
39: 222, //'
|
177
|
+
44: 188, //,
|
178
|
+
46: 190, //.
|
179
|
+
47: 191, ///
|
180
|
+
127: 46, //delete
|
181
|
+
126: 192, //~
|
182
|
+
33: 49, //!
|
183
|
+
64: 50, //@
|
184
|
+
35: 51, //#
|
185
|
+
36: 52, //$
|
186
|
+
37: 53, //%
|
187
|
+
94: 54, //^
|
188
|
+
38: 55, //&
|
189
|
+
42: 56, //*
|
190
|
+
40: 57, //(
|
191
|
+
41: 48, //)
|
192
|
+
95: 189, //_
|
193
|
+
43: 187, //+
|
194
|
+
123: 219, //{
|
195
|
+
125: 221, //}
|
196
|
+
124: 220, //|
|
197
|
+
58: 186, //:
|
198
|
+
34: 222, //"
|
199
|
+
60: 188, //<
|
200
|
+
62: 190, //>
|
201
|
+
63: 191 //?
|
202
|
+
};
|
203
|
+
if (specialKeys[code]) {
|
204
|
+
code = specialKeys[code];
|
205
|
+
}
|
206
|
+
return code;
|
207
|
+
},
|
208
|
+
|
209
|
+
set: function (index, value) {
|
210
|
+
var length, maxLength, node, strindex, textTypes, type;
|
211
|
+
|
212
|
+
node = this.nodes[index];
|
213
|
+
type = (node.type || node.tagName).toLowerCase();
|
214
|
+
textTypes = ["email", "number", "password", "search", "tel", "text", "textarea", "url"];
|
215
|
+
|
216
|
+
if (textTypes.indexOf(type) != -1) {
|
217
|
+
this.trigger(index, "focus");
|
218
|
+
|
219
|
+
maxLength = this.attribute(index, "maxlength");
|
220
|
+
if (maxLength && value.length > maxLength) {
|
221
|
+
length = maxLength;
|
222
|
+
} else {
|
223
|
+
length = value.length;
|
224
|
+
}
|
225
|
+
|
226
|
+
node.value = "";
|
227
|
+
for (strindex = 0; strindex < length; strindex++) {
|
228
|
+
node.value += value[strindex];
|
229
|
+
var keyCode = this.characterToKeyCode(value[strindex]);
|
230
|
+
this.keyupdown(index, "keydown", keyCode);
|
231
|
+
this.keypress(index, false, false, false, false, value.charCodeAt(strindex), value.charCodeAt(strindex));
|
232
|
+
this.keyupdown(index, "keyup", keyCode);
|
233
|
+
this.trigger(index, "input");
|
234
|
+
}
|
235
|
+
this.trigger(index, "change");
|
236
|
+
this.trigger(index, "blur");
|
237
|
+
|
238
|
+
} else if (type === "checkbox" || type === "radio") {
|
239
|
+
if (node.checked != (value === "true")) {
|
240
|
+
this.click(index)
|
241
|
+
}
|
242
|
+
|
243
|
+
} else if (type === "file") {
|
244
|
+
this.lastAttachedFile = value;
|
245
|
+
this.click(index)
|
246
|
+
|
247
|
+
} else {
|
248
|
+
node.value = value;
|
249
|
+
}
|
250
|
+
},
|
251
|
+
|
252
|
+
selectOption: function(index) {
|
253
|
+
this.nodes[index].selected = true;
|
254
|
+
this.trigger(index, "change");
|
255
|
+
},
|
256
|
+
|
257
|
+
unselectOption: function(index) {
|
258
|
+
this.nodes[index].selected = false;
|
259
|
+
this.trigger(index, "change");
|
260
|
+
},
|
261
|
+
|
262
|
+
centerPostion: function(element) {
|
263
|
+
this.reflow(element);
|
264
|
+
var rect = element.getBoundingClientRect();
|
265
|
+
var position = {
|
266
|
+
x: rect.width / 2,
|
267
|
+
y: rect.height / 2
|
268
|
+
};
|
269
|
+
do {
|
270
|
+
position.x += element.offsetLeft;
|
271
|
+
position.y += element.offsetTop;
|
272
|
+
} while ((element = element.offsetParent));
|
273
|
+
position.x = Math.floor(position.x), position.y = Math.floor(position.y);
|
274
|
+
|
275
|
+
return position;
|
276
|
+
},
|
277
|
+
|
278
|
+
reflow: function(element, force) {
|
279
|
+
if (force || element.offsetWidth === 0) {
|
280
|
+
var prop, oldStyle = {}, newStyle = {position: "absolute", visibility : "hidden", display: "block" };
|
281
|
+
for (prop in newStyle) {
|
282
|
+
oldStyle[prop] = element.style[prop];
|
283
|
+
element.style[prop] = newStyle[prop];
|
284
|
+
}
|
285
|
+
element.offsetWidth, element.offsetHeight; // force reflow
|
286
|
+
for (prop in oldStyle)
|
287
|
+
element.style[prop] = oldStyle[prop];
|
288
|
+
}
|
289
|
+
},
|
290
|
+
|
291
|
+
dragTo: function (index, targetIndex) {
|
292
|
+
var element = this.nodes[index], target = this.nodes[targetIndex];
|
293
|
+
var position = this.centerPostion(element);
|
294
|
+
var options = {
|
295
|
+
clientX: position.x,
|
296
|
+
clientY: position.y
|
297
|
+
};
|
298
|
+
var mouseTrigger = function(eventName, options) {
|
299
|
+
var eventObject = document.createEvent("MouseEvents");
|
300
|
+
eventObject.initMouseEvent(eventName, true, true, window, 0, 0, 0, options.clientX || 0, options.clientY || 0, false, false, false, false, 0, null);
|
301
|
+
element.dispatchEvent(eventObject);
|
302
|
+
}
|
303
|
+
mouseTrigger('mousedown', options);
|
304
|
+
options.clientX += 1, options.clientY += 1;
|
305
|
+
mouseTrigger('mousemove', options);
|
306
|
+
|
307
|
+
position = this.centerPostion(target), options = {
|
308
|
+
clientX: position.x,
|
309
|
+
clientY: position.y
|
310
|
+
};
|
311
|
+
mouseTrigger('mousemove', options);
|
312
|
+
mouseTrigger('mouseup', options);
|
313
|
+
}
|
314
|
+
};
|
315
|
+
|
data/src/find_command.h
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#define CHECK_COMMAND(expectedName) \
|
2
|
+
if (strcmp(#expectedName, name) == 0) { \
|
3
|
+
return new expectedName(m_page, arguments, this); \
|
4
|
+
}
|
5
|
+
|
6
|
+
CHECK_COMMAND(Visit)
|
7
|
+
CHECK_COMMAND(Find)
|
8
|
+
CHECK_COMMAND(Reset)
|
9
|
+
CHECK_COMMAND(Node)
|
10
|
+
CHECK_COMMAND(Url)
|
11
|
+
CHECK_COMMAND(Source)
|
12
|
+
CHECK_COMMAND(Evaluate)
|
13
|
+
CHECK_COMMAND(Execute)
|
14
|
+
CHECK_COMMAND(FrameFocus)
|
15
|
+
CHECK_COMMAND(Header)
|
16
|
+
CHECK_COMMAND(Render)
|
17
|
+
CHECK_COMMAND(Body)
|
18
|
+
CHECK_COMMAND(Status)
|
19
|
+
CHECK_COMMAND(Headers)
|
20
|
+
CHECK_COMMAND(SetCookie)
|
21
|
+
CHECK_COMMAND(ClearCookies)
|
22
|
+
CHECK_COMMAND(GetCookies)
|
23
|
+
CHECK_COMMAND(Headers)
|
24
|
+
CHECK_COMMAND(SetProxy)
|
25
|
+
CHECK_COMMAND(ConsoleMessages)
|
26
|
+
CHECK_COMMAND(RequestedUrl)
|
27
|
+
CHECK_COMMAND(CurrentUrl)
|
28
|
+
CHECK_COMMAND(ResizeWindow)
|
29
|
+
CHECK_COMMAND(IgnoreSslErrors)
|
data/src/main.cpp
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#include "Server.h"
|
2
|
+
#include <QtGui>
|
3
|
+
#include <iostream>
|
4
|
+
#ifdef Q_OS_UNIX
|
5
|
+
#include <unistd.h>
|
6
|
+
#endif
|
7
|
+
|
8
|
+
int main(int argc, char **argv) {
|
9
|
+
#ifdef Q_OS_UNIX
|
10
|
+
if (setpgid(0, 0) < 0) {
|
11
|
+
std::cerr << "Unable to set new process group." << std::endl;
|
12
|
+
return 1;
|
13
|
+
}
|
14
|
+
#endif
|
15
|
+
|
16
|
+
QApplication app(argc, argv);
|
17
|
+
app.setApplicationName("capybara-webkit");
|
18
|
+
app.setOrganizationName("thoughtbot, inc");
|
19
|
+
app.setOrganizationDomain("thoughtbot.com");
|
20
|
+
|
21
|
+
QStringList args = app.arguments();
|
22
|
+
|
23
|
+
Server server(0);
|
24
|
+
|
25
|
+
if (server.start()) {
|
26
|
+
std::cout << "Capybara-webkit server started, listening on port: " << server.server_port() << std::endl;
|
27
|
+
return app.exec();
|
28
|
+
} else {
|
29
|
+
std::cerr << "Couldn't start capybara-webkit server" << std::endl;
|
30
|
+
return 1;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|