otherinbox-capybara-webkit 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +2 -0
  3. data/Appraisals +7 -0
  4. data/CONTRIBUTING.md +47 -0
  5. data/ChangeLog +70 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +68 -0
  8. data/LICENSE +19 -0
  9. data/NEWS.md +36 -0
  10. data/README.md +114 -0
  11. data/Rakefile +65 -0
  12. data/bin/Info.plist +22 -0
  13. data/capybara-webkit.gemspec +28 -0
  14. data/extconf.rb +2 -0
  15. data/gemfiles/1.0.gemfile +7 -0
  16. data/gemfiles/1.0.gemfile.lock +70 -0
  17. data/gemfiles/1.1.gemfile +7 -0
  18. data/gemfiles/1.1.gemfile.lock +70 -0
  19. data/lib/capybara-webkit.rb +1 -0
  20. data/lib/capybara/driver/webkit.rb +135 -0
  21. data/lib/capybara/driver/webkit/browser.rb +168 -0
  22. data/lib/capybara/driver/webkit/connection.rb +120 -0
  23. data/lib/capybara/driver/webkit/cookie_jar.rb +55 -0
  24. data/lib/capybara/driver/webkit/node.rb +118 -0
  25. data/lib/capybara/driver/webkit/socket_debugger.rb +43 -0
  26. data/lib/capybara/driver/webkit/version.rb +7 -0
  27. data/lib/capybara/webkit.rb +11 -0
  28. data/lib/capybara/webkit/matchers.rb +37 -0
  29. data/lib/capybara_webkit_builder.rb +68 -0
  30. data/spec/browser_spec.rb +248 -0
  31. data/spec/capybara_webkit_builder_spec.rb +37 -0
  32. data/spec/connection_spec.rb +54 -0
  33. data/spec/cookie_jar_spec.rb +48 -0
  34. data/spec/driver_rendering_spec.rb +80 -0
  35. data/spec/driver_resize_window_spec.rb +59 -0
  36. data/spec/driver_spec.rb +1552 -0
  37. data/spec/integration/driver_spec.rb +20 -0
  38. data/spec/integration/session_spec.rb +137 -0
  39. data/spec/self_signed_ssl_cert.rb +42 -0
  40. data/spec/spec_helper.rb +46 -0
  41. data/src/Body.h +12 -0
  42. data/src/ClearCookies.cpp +15 -0
  43. data/src/ClearCookies.h +11 -0
  44. data/src/Command.cpp +19 -0
  45. data/src/Command.h +31 -0
  46. data/src/CommandFactory.cpp +38 -0
  47. data/src/CommandFactory.h +16 -0
  48. data/src/CommandParser.cpp +76 -0
  49. data/src/CommandParser.h +33 -0
  50. data/src/Connection.cpp +71 -0
  51. data/src/Connection.h +37 -0
  52. data/src/ConsoleMessages.cpp +10 -0
  53. data/src/ConsoleMessages.h +12 -0
  54. data/src/CurrentUrl.cpp +68 -0
  55. data/src/CurrentUrl.h +16 -0
  56. data/src/Evaluate.cpp +84 -0
  57. data/src/Evaluate.h +22 -0
  58. data/src/Execute.cpp +16 -0
  59. data/src/Execute.h +12 -0
  60. data/src/Find.cpp +19 -0
  61. data/src/Find.h +13 -0
  62. data/src/FrameFocus.cpp +66 -0
  63. data/src/FrameFocus.h +28 -0
  64. data/src/GetCookies.cpp +20 -0
  65. data/src/GetCookies.h +14 -0
  66. data/src/Header.cpp +18 -0
  67. data/src/Header.h +11 -0
  68. data/src/Headers.cpp +10 -0
  69. data/src/Headers.h +12 -0
  70. data/src/IgnoreSslErrors.cpp +12 -0
  71. data/src/IgnoreSslErrors.h +12 -0
  72. data/src/JavascriptInvocation.cpp +14 -0
  73. data/src/JavascriptInvocation.h +19 -0
  74. data/src/NetworkAccessManager.cpp +29 -0
  75. data/src/NetworkAccessManager.h +19 -0
  76. data/src/NetworkCookieJar.cpp +101 -0
  77. data/src/NetworkCookieJar.h +15 -0
  78. data/src/Node.cpp +14 -0
  79. data/src/Node.h +13 -0
  80. data/src/NullCommand.cpp +10 -0
  81. data/src/NullCommand.h +11 -0
  82. data/src/PageLoadingCommand.cpp +46 -0
  83. data/src/PageLoadingCommand.h +40 -0
  84. data/src/Render.cpp +18 -0
  85. data/src/Render.h +12 -0
  86. data/src/RequestedUrl.cpp +12 -0
  87. data/src/RequestedUrl.h +12 -0
  88. data/src/Reset.cpp +29 -0
  89. data/src/Reset.h +15 -0
  90. data/src/ResizeWindow.cpp +16 -0
  91. data/src/ResizeWindow.h +12 -0
  92. data/src/Response.cpp +24 -0
  93. data/src/Response.h +15 -0
  94. data/src/Server.cpp +24 -0
  95. data/src/Server.h +21 -0
  96. data/src/SetCookie.cpp +16 -0
  97. data/src/SetCookie.h +11 -0
  98. data/src/SetProxy.cpp +22 -0
  99. data/src/SetProxy.h +11 -0
  100. data/src/SetSkipImageLoading.cpp +11 -0
  101. data/src/SetSkipImageLoading.h +11 -0
  102. data/src/Source.cpp +18 -0
  103. data/src/Source.h +19 -0
  104. data/src/Status.cpp +12 -0
  105. data/src/Status.h +12 -0
  106. data/src/UnsupportedContentHandler.cpp +32 -0
  107. data/src/UnsupportedContentHandler.h +18 -0
  108. data/src/Url.cpp +12 -0
  109. data/src/Url.h +12 -0
  110. data/src/Visit.cpp +12 -0
  111. data/src/Visit.h +12 -0
  112. data/src/WebPage.cpp +246 -0
  113. data/src/WebPage.h +58 -0
  114. data/src/body.cpp +10 -0
  115. data/src/capybara.js +315 -0
  116. data/src/find_command.h +30 -0
  117. data/src/main.cpp +31 -0
  118. data/src/webkit_server.pro +87 -0
  119. data/src/webkit_server.qrc +5 -0
  120. data/templates/Command.cpp +10 -0
  121. data/templates/Command.h +12 -0
  122. data/webkit_server.pro +4 -0
  123. metadata +300 -0
@@ -0,0 +1,18 @@
1
+ #include <QObject>
2
+ class WebPage;
3
+ class QNetworkReply;
4
+ class UnsupportedContentHandler : public QObject {
5
+ Q_OBJECT
6
+
7
+ public:
8
+ UnsupportedContentHandler(WebPage *page, QNetworkReply *reply, QObject *parent = 0);
9
+
10
+ public slots:
11
+ void handleUnsupportedContent();
12
+
13
+ private:
14
+ WebPage *m_page;
15
+ QNetworkReply *m_reply;
16
+ void loadUnsupportedContent();
17
+ void finish(bool success);
18
+ };
data/src/Url.cpp ADDED
@@ -0,0 +1,12 @@
1
+ #include "Url.h"
2
+ #include "WebPage.h"
3
+
4
+ Url::Url(WebPage *page, QStringList &arguments, QObject *parent) : Command(page, arguments, parent) {
5
+ }
6
+
7
+ void Url::start() {
8
+ QUrl humanUrl = page()->currentFrame()->url();
9
+ QByteArray encodedBytes = humanUrl.toEncoded();
10
+ emit finished(new Response(true, encodedBytes));
11
+ }
12
+
data/src/Url.h ADDED
@@ -0,0 +1,12 @@
1
+ #include "Command.h"
2
+
3
+ class WebPage;
4
+
5
+ class Url : public Command {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ Url(WebPage *page, QStringList &arguments, QObject *parent = 0);
10
+ virtual void start();
11
+ };
12
+
data/src/Visit.cpp ADDED
@@ -0,0 +1,12 @@
1
+ #include "Visit.h"
2
+ #include "Command.h"
3
+ #include "WebPage.h"
4
+
5
+ Visit::Visit(WebPage *page, QStringList &arguments, QObject *parent) : Command(page, arguments, parent) {
6
+ }
7
+
8
+ void Visit::start() {
9
+ QUrl requestedUrl = QUrl::fromEncoded(arguments()[0].toUtf8(), QUrl::StrictMode);
10
+ page()->currentFrame()->load(QUrl(requestedUrl));
11
+ emit finished(new Response(true));
12
+ }
data/src/Visit.h ADDED
@@ -0,0 +1,12 @@
1
+ #include "Command.h"
2
+
3
+ class WebPage;
4
+
5
+ class Visit : public Command {
6
+ Q_OBJECT
7
+
8
+ public:
9
+ Visit(WebPage *page, QStringList &arguments, QObject *parent = 0);
10
+ virtual void start();
11
+ };
12
+
data/src/WebPage.cpp ADDED
@@ -0,0 +1,246 @@
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
+ #include <QWebSettings>
9
+
10
+ WebPage::WebPage(QObject *parent) : QWebPage(parent) {
11
+ setForwardUnsupportedContent(true);
12
+ loadJavascript();
13
+ setUserStylesheet();
14
+
15
+ m_loading = false;
16
+ m_ignoreSslErrors = false;
17
+ this->setCustomNetworkAccessManager();
18
+
19
+ this->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
20
+
21
+ connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
22
+ connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
23
+ connect(this, SIGNAL(frameCreated(QWebFrame *)),
24
+ this, SLOT(frameCreated(QWebFrame *)));
25
+ connect(this, SIGNAL(unsupportedContent(QNetworkReply*)),
26
+ this, SLOT(handleUnsupportedContent(QNetworkReply*)));
27
+ resetWindowSize();
28
+ }
29
+
30
+ void WebPage::resetWindowSize() {
31
+ this->setViewportSize(QSize(1680, 1050));
32
+ this->settings()->setAttribute(QWebSettings::LocalStorageDatabaseEnabled, true);
33
+ }
34
+
35
+ void WebPage::setCustomNetworkAccessManager() {
36
+ NetworkAccessManager *manager = new NetworkAccessManager();
37
+ manager->setCookieJar(new NetworkCookieJar());
38
+ this->setNetworkAccessManager(manager);
39
+ connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *)));
40
+ connect(manager, SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
41
+ this, SLOT(handleSslErrorsForReply(QNetworkReply *, QList<QSslError>)));
42
+ }
43
+
44
+ void WebPage::loadJavascript() {
45
+ QResource javascript(":/capybara.js");
46
+ if (javascript.isCompressed()) {
47
+ QByteArray uncompressedBytes(qUncompress(javascript.data(), javascript.size()));
48
+ m_capybaraJavascript = QString(uncompressedBytes);
49
+ } else {
50
+ char * javascriptString = new char[javascript.size() + 1];
51
+ strcpy(javascriptString, (const char *)javascript.data());
52
+ javascriptString[javascript.size()] = 0;
53
+ m_capybaraJavascript = javascriptString;
54
+ }
55
+ }
56
+
57
+ void WebPage::setUserStylesheet() {
58
+ QString data = QString("* { font-family: 'Arial' ! important; }").toUtf8().toBase64();
59
+ QUrl url = QUrl(QString("data:text/css;charset=utf-8;base64,") + data);
60
+ settings()->setUserStyleSheetUrl(url);
61
+ }
62
+
63
+ QString WebPage::userAgentForUrl(const QUrl &url ) const {
64
+ if (!m_userAgent.isEmpty()) {
65
+ return m_userAgent;
66
+ } else {
67
+ return QWebPage::userAgentForUrl(url);
68
+ }
69
+ }
70
+
71
+ QString WebPage::consoleMessages() {
72
+ return m_consoleMessages.join("\n");
73
+ }
74
+
75
+ void WebPage::setUserAgent(QString userAgent) {
76
+ m_userAgent = userAgent;
77
+ }
78
+
79
+ void WebPage::frameCreated(QWebFrame * frame) {
80
+ connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
81
+ this, SLOT(injectJavascriptHelpers()));
82
+ }
83
+
84
+ void WebPage::injectJavascriptHelpers() {
85
+ QWebFrame* frame = qobject_cast<QWebFrame *>(QObject::sender());
86
+ frame->evaluateJavaScript(m_capybaraJavascript);
87
+ }
88
+
89
+ bool WebPage::shouldInterruptJavaScript() {
90
+ return false;
91
+ }
92
+
93
+ QVariant WebPage::invokeCapybaraFunction(const char *name, QStringList &arguments) {
94
+ QString qname(name);
95
+ QString objectName("CapybaraInvocation");
96
+ JavascriptInvocation invocation(qname, arguments);
97
+ currentFrame()->addToJavaScriptWindowObject(objectName, &invocation);
98
+ QString javascript = QString("Capybara.invoke()");
99
+ return currentFrame()->evaluateJavaScript(javascript);
100
+ }
101
+
102
+ QVariant WebPage::invokeCapybaraFunction(QString &name, QStringList &arguments) {
103
+ return invokeCapybaraFunction(name.toAscii().data(), arguments);
104
+ }
105
+
106
+ void WebPage::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID) {
107
+ QString fullMessage = QString::number(lineNumber) + "|" + message;
108
+ if (!sourceID.isEmpty())
109
+ fullMessage = sourceID + "|" + fullMessage;
110
+ m_consoleMessages.append(fullMessage);
111
+ std::cout << qPrintable(fullMessage) << std::endl;
112
+ }
113
+
114
+ void WebPage::javaScriptAlert(QWebFrame *frame, const QString &message) {
115
+ Q_UNUSED(frame);
116
+ std::cout << "ALERT: " << qPrintable(message) << std::endl;
117
+ }
118
+
119
+ bool WebPage::javaScriptConfirm(QWebFrame *frame, const QString &message) {
120
+ Q_UNUSED(frame);
121
+ Q_UNUSED(message);
122
+ return true;
123
+ }
124
+
125
+ bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result) {
126
+ Q_UNUSED(frame)
127
+ Q_UNUSED(message)
128
+ Q_UNUSED(defaultValue)
129
+ Q_UNUSED(result)
130
+ return false;
131
+ }
132
+
133
+ void WebPage::loadStarted() {
134
+ m_loading = true;
135
+ }
136
+
137
+ void WebPage::loadFinished(bool success) {
138
+ m_loading = false;
139
+ emit pageFinished(success);
140
+ }
141
+
142
+ bool WebPage::isLoading() const {
143
+ return m_loading;
144
+ }
145
+
146
+ QString WebPage::failureString() {
147
+ return QString("Unable to load URL: ") + currentFrame()->requestedUrl().toString();
148
+ }
149
+
150
+ bool WebPage::render(const QString &fileName) {
151
+ QFileInfo fileInfo(fileName);
152
+ QDir dir;
153
+ dir.mkpath(fileInfo.absolutePath());
154
+
155
+ QSize viewportSize = this->viewportSize();
156
+ QSize pageSize = this->mainFrame()->contentsSize();
157
+ if (pageSize.isEmpty()) {
158
+ return false;
159
+ }
160
+
161
+ QImage buffer(pageSize, QImage::Format_ARGB32);
162
+ buffer.fill(qRgba(255, 255, 255, 0));
163
+
164
+ QPainter p(&buffer);
165
+ p.setRenderHint( QPainter::Antialiasing, true);
166
+ p.setRenderHint( QPainter::TextAntialiasing, true);
167
+ p.setRenderHint( QPainter::SmoothPixmapTransform, true);
168
+
169
+ this->setViewportSize(pageSize);
170
+ this->mainFrame()->render(&p);
171
+ p.end();
172
+ this->setViewportSize(viewportSize);
173
+
174
+ return buffer.save(fileName);
175
+ }
176
+
177
+ QString WebPage::chooseFile(QWebFrame *parentFrame, const QString &suggestedFile) {
178
+ Q_UNUSED(parentFrame);
179
+ Q_UNUSED(suggestedFile);
180
+
181
+ return getLastAttachedFileName();
182
+ }
183
+
184
+ bool WebPage::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) {
185
+ Q_UNUSED(option);
186
+ if (extension == ChooseMultipleFilesExtension) {
187
+ QStringList names = QStringList() << getLastAttachedFileName();
188
+ static_cast<ChooseMultipleFilesExtensionReturn*>(output)->fileNames = names;
189
+ return true;
190
+ }
191
+ return false;
192
+ }
193
+
194
+ QString WebPage::getLastAttachedFileName() {
195
+ return currentFrame()->evaluateJavaScript(QString("Capybara.lastAttachedFile")).toString();
196
+ }
197
+
198
+ void WebPage::replyFinished(QNetworkReply *reply) {
199
+ if (reply->url() == this->currentFrame()->url()) {
200
+ QStringList headers;
201
+ m_lastStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
202
+ QList<QByteArray> list = reply->rawHeaderList();
203
+
204
+ int length = list.size();
205
+ for(int i = 0; i < length; i++) {
206
+ headers << list.at(i)+": "+reply->rawHeader(list.at(i));
207
+ }
208
+
209
+ m_pageHeaders = headers.join("\n");
210
+ }
211
+ }
212
+
213
+ void WebPage::handleSslErrorsForReply(QNetworkReply *reply, const QList<QSslError> &errors) {
214
+ if (m_ignoreSslErrors)
215
+ reply->ignoreSslErrors(errors);
216
+ }
217
+
218
+ void WebPage::ignoreSslErrors() {
219
+ m_ignoreSslErrors = true;
220
+ }
221
+
222
+ void WebPage::setSkipImageLoading(bool skip) {
223
+ settings()->setAttribute(QWebSettings::AutoLoadImages, !skip);
224
+ }
225
+
226
+ int WebPage::getLastStatus() {
227
+ return m_lastStatus;
228
+ }
229
+
230
+ void WebPage::resetResponseHeaders() {
231
+ m_lastStatus = 0;
232
+ m_pageHeaders = QString();
233
+ }
234
+
235
+ void WebPage::resetConsoleMessages() {
236
+ m_consoleMessages.clear();
237
+ }
238
+
239
+ QString WebPage::pageHeaders() {
240
+ return m_pageHeaders;
241
+ }
242
+
243
+ void WebPage::handleUnsupportedContent(QNetworkReply *reply) {
244
+ UnsupportedContentHandler *handler = new UnsupportedContentHandler(this, reply);
245
+ Q_UNUSED(handler);
246
+ }
data/src/WebPage.h ADDED
@@ -0,0 +1,58 @@
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
+ QString userAgentForUrl(const QUrl &url ) const;
12
+ void setUserAgent(QString userAgent);
13
+ int getLastStatus();
14
+ void resetResponseHeaders();
15
+ void setCustomNetworkAccessManager();
16
+ bool render(const QString &fileName);
17
+ virtual bool extension (Extension extension, const ExtensionOption *option=0, ExtensionReturn *output=0);
18
+ void ignoreSslErrors();
19
+ void setSkipImageLoading(bool skip);
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
+