2011-04-11 10:24:07 +00:00
|
|
|
/*
|
|
|
|
Copyright (c) 2010 Sencha Inc.
|
|
|
|
Copyright (c) 2011 John Bintz
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
|
|
all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <QtGui>
|
|
|
|
#include <QtWebKit>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
|
|
|
|
#error Use Qt 4.7 or later version
|
|
|
|
#endif
|
|
|
|
|
2011-04-12 11:10:03 +00:00
|
|
|
class HeadlessSpecRunnerPage: public QWebPage
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
signals:
|
|
|
|
void consoleLog(const QString &msg, int lineNumber, const QString &sourceID);
|
2011-05-03 15:22:18 +00:00
|
|
|
void internalLog(const QString ¬e, const QString &msg);
|
2011-04-12 11:10:03 +00:00
|
|
|
protected:
|
|
|
|
void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID);
|
2011-05-03 15:06:49 +00:00
|
|
|
bool javaScriptConfirm(QWebFrame *frame, const QString &msg);
|
|
|
|
void javaScriptAlert(QWebFrame *frame, const QString &msg);
|
2011-04-12 11:10:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
void HeadlessSpecRunnerPage::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID)
|
|
|
|
{
|
|
|
|
emit consoleLog(message, lineNumber, sourceID);
|
|
|
|
}
|
|
|
|
|
2011-05-03 15:06:49 +00:00
|
|
|
bool HeadlessSpecRunnerPage::javaScriptConfirm(QWebFrame *frame, const QString &msg)
|
|
|
|
{
|
2011-05-03 15:22:18 +00:00
|
|
|
emit internalLog("TODO", "jasmine-headless-webkit can't handle confirm() yet! You should mock window.confirm for now. Returning true.");
|
2011-05-03 15:06:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeadlessSpecRunnerPage::javaScriptAlert(QWebFrame *frame, const QString &msg)
|
|
|
|
{
|
2011-05-03 15:22:18 +00:00
|
|
|
emit internalLog("alert", msg);
|
2011-05-03 15:06:49 +00:00
|
|
|
}
|
|
|
|
|
2011-04-11 10:24:07 +00:00
|
|
|
class HeadlessSpecRunner: public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
HeadlessSpecRunner();
|
|
|
|
void load(const QString &spec);
|
2011-05-02 16:00:03 +00:00
|
|
|
void setColors(bool colors);
|
2011-04-11 10:24:07 +00:00
|
|
|
public slots:
|
|
|
|
void log(const QString &msg);
|
|
|
|
void specLog(int indent, const QString &msg, const QString &clazz);
|
|
|
|
private slots:
|
|
|
|
void watch(bool ok);
|
2011-04-12 11:10:03 +00:00
|
|
|
void errorLog(const QString &msg, int lineNumber, const QString &sourceID);
|
2011-05-03 15:22:18 +00:00
|
|
|
void internalLog(const QString ¬e, const QString &msg);
|
2011-04-11 10:24:07 +00:00
|
|
|
protected:
|
|
|
|
bool hasElement(const char *select);
|
|
|
|
void timerEvent(QTimerEvent *event);
|
|
|
|
private:
|
2011-04-12 11:10:03 +00:00
|
|
|
HeadlessSpecRunnerPage m_page;
|
2011-04-11 10:24:07 +00:00
|
|
|
QBasicTimer m_ticker;
|
|
|
|
int m_runs;
|
2011-04-12 11:10:03 +00:00
|
|
|
bool hasErrors;
|
2011-04-14 00:21:23 +00:00
|
|
|
bool usedConsole;
|
2011-05-02 16:00:03 +00:00
|
|
|
bool showColors;
|
|
|
|
|
|
|
|
void red();
|
|
|
|
void green();
|
|
|
|
void yellow();
|
|
|
|
void clear();
|
2011-04-11 10:24:07 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
HeadlessSpecRunner::HeadlessSpecRunner()
|
|
|
|
: QObject()
|
|
|
|
, m_runs(0)
|
2011-04-12 11:28:06 +00:00
|
|
|
, hasErrors(false)
|
2011-04-14 00:21:23 +00:00
|
|
|
, usedConsole(false)
|
2011-05-02 16:00:03 +00:00
|
|
|
, showColors(false)
|
2011-04-11 10:24:07 +00:00
|
|
|
{
|
|
|
|
m_page.settings()->enablePersistentStorage();
|
|
|
|
connect(&m_page, SIGNAL(loadFinished(bool)), this, SLOT(watch(bool)));
|
2011-04-12 11:10:03 +00:00
|
|
|
connect(&m_page, SIGNAL(consoleLog(QString, int, QString)), this, SLOT(errorLog(QString, int, QString)));
|
2011-05-03 15:22:18 +00:00
|
|
|
connect(&m_page, SIGNAL(internalLog(QString, QString)), this, SLOT(internalLog(QString, QString)));
|
2011-04-11 10:24:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void HeadlessSpecRunner::load(const QString &spec)
|
|
|
|
{
|
|
|
|
m_ticker.stop();
|
|
|
|
m_page.mainFrame()->addToJavaScriptWindowObject("debug", this);
|
|
|
|
m_page.mainFrame()->load(spec);
|
|
|
|
m_page.setPreferredContentsSize(QSize(1024, 600));
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeadlessSpecRunner::watch(bool ok)
|
|
|
|
{
|
|
|
|
if (!ok) {
|
2011-05-06 11:20:18 +00:00
|
|
|
std::cerr << "Can't load " << qPrintable(m_page.mainFrame()->url().toString()) << ", the file may be broken." << std::endl;
|
|
|
|
std::cerr << "Out of curiosity, did your tests try to submit a form and you haven't prevented that?" << std::endl;
|
|
|
|
std::cerr << "Try running your tests in your browser with the Jasmine server and see what happens." << std::endl;
|
2011-04-11 10:24:07 +00:00
|
|
|
QApplication::instance()->exit(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ticker.start(200, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HeadlessSpecRunner::hasElement(const char *select)
|
|
|
|
{
|
|
|
|
return !m_page.mainFrame()->findFirstElement(select).isNull();
|
|
|
|
}
|
|
|
|
|
2011-05-02 16:00:03 +00:00
|
|
|
void HeadlessSpecRunner::setColors(bool colors)
|
|
|
|
{
|
|
|
|
showColors = colors;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeadlessSpecRunner::red()
|
|
|
|
{
|
|
|
|
if (showColors) std::cout << "\033[0;31m";
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeadlessSpecRunner::green()
|
|
|
|
{
|
|
|
|
if (showColors) std::cout << "\033[0;32m";
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeadlessSpecRunner::yellow()
|
|
|
|
{
|
|
|
|
if (showColors) std::cout << "\033[0;33m";
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeadlessSpecRunner::clear()
|
|
|
|
{
|
|
|
|
if (showColors) std::cout << "\033[m";
|
|
|
|
}
|
|
|
|
|
2011-04-12 11:10:03 +00:00
|
|
|
void HeadlessSpecRunner::errorLog(const QString &msg, int lineNumber, const QString &sourceID)
|
|
|
|
{
|
2011-05-02 16:00:03 +00:00
|
|
|
red();
|
|
|
|
std::cout << "[error] ";
|
|
|
|
clear();
|
2011-04-12 11:10:03 +00:00
|
|
|
std::cout << qPrintable(sourceID) << ":" << lineNumber << " : " << qPrintable(msg);
|
|
|
|
std::cout << std::endl;
|
2011-04-12 11:28:06 +00:00
|
|
|
|
2011-04-12 11:10:03 +00:00
|
|
|
hasErrors = true;
|
2011-04-12 11:29:00 +00:00
|
|
|
m_runs = 0;
|
2011-04-12 11:28:06 +00:00
|
|
|
m_ticker.start(200, this);
|
2011-04-12 11:10:03 +00:00
|
|
|
}
|
|
|
|
|
2011-05-03 15:22:18 +00:00
|
|
|
void HeadlessSpecRunner::internalLog(const QString ¬e, const QString &msg) {
|
|
|
|
red();
|
|
|
|
std::cout << "[" << qPrintable(note) << "] ";
|
|
|
|
clear();
|
|
|
|
std::cout << qPrintable(msg);
|
|
|
|
std::cout << std::endl;
|
|
|
|
}
|
|
|
|
|
2011-04-11 10:24:07 +00:00
|
|
|
void HeadlessSpecRunner::log(const QString &msg)
|
|
|
|
{
|
2011-04-14 00:21:23 +00:00
|
|
|
usedConsole = true;
|
2011-05-02 16:00:03 +00:00
|
|
|
green();
|
|
|
|
std::cout << "[console] ";
|
|
|
|
clear();
|
2011-04-11 10:24:07 +00:00
|
|
|
std::cout << qPrintable(msg);
|
|
|
|
std::cout << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeadlessSpecRunner::specLog(int indent, const QString &msg, const QString &clazz)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < indent; ++i)
|
|
|
|
std::cout << " ";
|
|
|
|
if ( clazz.endsWith("fail") ) {
|
2011-05-02 16:00:03 +00:00
|
|
|
red();
|
2011-04-11 10:24:07 +00:00
|
|
|
} else {
|
2011-05-02 20:59:14 +00:00
|
|
|
yellow();
|
2011-04-11 10:24:07 +00:00
|
|
|
}
|
2011-05-02 16:00:03 +00:00
|
|
|
std::cout << qPrintable(msg);
|
|
|
|
clear();
|
2011-04-11 10:24:07 +00:00
|
|
|
std::cout << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define DUMP_MSG "(function(n, i) { \
|
|
|
|
if (n.toString() === '[object NodeList]') { \
|
|
|
|
for (var c = 0; c < n.length; ++c) arguments.callee(n[c], i); return \
|
|
|
|
}\
|
|
|
|
if (n.className === 'description' || n.className == 'resultMessage fail') {\
|
|
|
|
debug.specLog(i, n.textContent, n.className);\
|
|
|
|
}\
|
|
|
|
var e = n.firstElementChild;\
|
|
|
|
while (e) {\
|
|
|
|
arguments.callee(e, i + 1); e = e.nextElementSibling; \
|
|
|
|
}\
|
|
|
|
n.className = '';\
|
|
|
|
})(document.getElementsByClassName('suite failed'), 0);"
|
|
|
|
|
|
|
|
void HeadlessSpecRunner::timerEvent(QTimerEvent *event)
|
|
|
|
{
|
2011-04-12 11:28:06 +00:00
|
|
|
++m_runs;
|
|
|
|
|
2011-04-11 10:24:07 +00:00
|
|
|
if (event->timerId() != m_ticker.timerId())
|
|
|
|
return;
|
|
|
|
|
2011-04-12 11:29:00 +00:00
|
|
|
if (hasErrors && m_runs > 2)
|
2011-04-12 11:28:06 +00:00
|
|
|
QApplication::instance()->exit(1);
|
|
|
|
|
2011-04-12 11:32:04 +00:00
|
|
|
if (!hasErrors) {
|
|
|
|
if (!hasElement(".jasmine_reporter") && !hasElement(".runner.running"))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (hasElement(".runner.passed")) {
|
|
|
|
QWebElement desc = m_page.mainFrame()->findFirstElement(".description");
|
2011-05-02 16:00:03 +00:00
|
|
|
green();
|
|
|
|
std::cout << "PASS: " << qPrintable(desc.toPlainText());
|
|
|
|
clear();
|
|
|
|
std::cout << std::endl;
|
2011-04-14 00:21:23 +00:00
|
|
|
QApplication::instance()->exit(usedConsole ? 2 : 0);
|
2011-04-12 11:32:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasElement(".runner.failed")) {
|
|
|
|
QWebElement desc = m_page.mainFrame()->findFirstElement(".description");
|
2011-05-02 16:00:03 +00:00
|
|
|
red();
|
|
|
|
std::cout << "FAIL: " << qPrintable(desc.toPlainText());
|
|
|
|
clear();
|
|
|
|
std::cout << std::endl;
|
2011-04-12 11:32:04 +00:00
|
|
|
m_page.mainFrame()->evaluateJavaScript(DUMP_MSG);
|
|
|
|
QApplication::instance()->exit(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_runs > 30) {
|
|
|
|
std::cout << "WARNING: too many runs and the test is still not finished!" << std::endl;
|
|
|
|
QApplication::instance()->exit(1);
|
|
|
|
}
|
2011-04-11 10:24:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "specrunner.moc"
|
|
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
|
|
{
|
2011-05-02 16:00:03 +00:00
|
|
|
bool showColors = false;
|
|
|
|
char *filename = NULL;
|
|
|
|
|
|
|
|
int c, index;
|
|
|
|
|
|
|
|
while ((c = getopt(argc, argv, "c")) != -1) {
|
|
|
|
switch(c) {
|
|
|
|
case 'c':
|
|
|
|
showColors = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool filenameFound = false;
|
|
|
|
|
|
|
|
for (index = optind; index < argc; index++) {
|
|
|
|
filename = argv[index];
|
|
|
|
filenameFound = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!filenameFound) {
|
2011-04-11 10:24:07 +00:00
|
|
|
std::cerr << "Run Jasmine's SpecRunner headlessly" << std::endl << std::endl;
|
2011-05-02 16:00:03 +00:00
|
|
|
std::cerr << " specrunner [-c] SpecRunner.html" << std::endl;
|
2011-04-11 10:24:07 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
QApplication app(argc, argv);
|
|
|
|
|
|
|
|
HeadlessSpecRunner runner;
|
2011-05-02 16:00:03 +00:00
|
|
|
runner.setColors(showColors);
|
|
|
|
runner.load(QString::fromLocal8Bit(filename));
|
2011-04-11 10:24:07 +00:00
|
|
|
return app.exec();
|
|
|
|
}
|
|
|
|
|