initial sources

master
Marc Wäckerlin 11 years ago
parent a928df3fc7
commit 5f27a82624
  1. 1466
      src/commands.hxx
  2. 202
      src/exceptions.hxx
  3. 212
      src/networkaccessmanager.hxx
  4. 473
      src/testgui.hxx
  5. 874
      src/testgui.ui
  6. 37
      src/web.ui
  7. 70
      src/webpage.hxx
  8. 178
      src/webrunner.cxx
  9. 9
      src/webtester.cxx
  10. 30
      src/webtester.pro

@ -0,0 +1,1466 @@
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef COMMANDS_HXX
#define COMMANDS_HXX
#include <exceptions.hxx>
#include <webpage.hxx>
#include <QNetworkReply>
#include <QCoreApplication>
#include <QStringList>
#include <QWebFrame>
#include <QWebView>
#include <QWebElement>
#include <QPainter>
#include <QImage>
#include <QTimer>
#include <QProcess>
#include <QMouseEvent>
#include <vector>
#include <queue>
#include <map>
#include <memory>
#include <sstream>
#include <cassert>
#include <xml-cxx/xml.hxx>
class Script;
class Command;
class Logger {
public:
Logger(Command* command, Script* script);
void plainlog(QString txt);
void log(QString txt);
~Logger();
private:
Command* _command;
Script* _script;
};
class Command: public QObject {
Q_OBJECT;
public:
Command(): _log(true), _line(-1) {}
virtual ~Command() {}
virtual QString tag() const = 0;
virtual QString description() const = 0;
virtual QString command() const = 0;
virtual std::shared_ptr<Command> parse(Script*, QString,
QStringList&, int) = 0;
virtual bool execute(Script*, QWebFrame*) = 0;
void line(int linenr) {
_line = linenr;
}
int line() const {
return _line;
}
void testsuite(QString name) {
_testsuite = name;
}
QString testsuite() {
return _testsuite;
}
void targetdir(QString name) {
_targetdir = name;
}
QString targetdir() {
return _targetdir;
}
bool log() {
return _log;
}
void log(bool l) {
_log = l;
}
QString result() {
return _result;
}
protected:
void sleep(int s) {
QTime dieTime= QTime::currentTime().addSecs(s);
while (QTime::currentTime()<dieTime)
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
QWebElement find(QWebFrame* frame, QString selector, Logger& log,
int repeat = 2, int sleepsec = 1) {
QWebElement element;
element = find1(frame, selector, log, repeat, sleepsec);
if (!element.isNull()) return element;
element = find1(frame->page()->currentFrame(), selector, log, repeat, sleepsec);
if (!element.isNull()) return element;
element = find1(frame->page()->mainFrame(), selector, log, repeat, sleepsec);
if (!element.isNull()) return element;
return element;
}
QWebElement find1(QWebFrame* frame, QString selector, Logger& log,
int repeat = 5, int sleepsec = 1) {
QWebElement element;
for (int i=0; i<repeat; ++i) {
element = frame->findFirstElement(selector);
if (!element.isNull()) return element;
Q_FOREACH(QWebFrame* childFrame, frame->childFrames()) {
element = find1(childFrame, selector, log, 1, 0);
if (!element.isNull()) return element;
}
if (sleepsec) sleep(sleepsec);
}
return element;
}
void realMouseClick(QWebFrame* frame, QString selector, Logger& log) {
QWebElement element(find(frame, selector, log));
if (element.isNull()) throw ElementNotFound(selector);
QWidget* web(frame->page()->view());
QRect elGeom=element.geometry();
QPoint elPoint=elGeom.center();
int elX=elPoint.x();
int elY=elPoint.y();
int webWidth=web->width();
int webHeight=web->height();
int pixelsToScrolRight=0;
int pixelsToScrolDown=0;
if (elX>webWidth)
pixelsToScrolRight = //the +10 scrolls a bit further
elX-webWidth+elGeom.width()/2+10;
if (elY>webHeight)
pixelsToScrolDown = //the +10 scrolls a bit further
elY-webHeight+elGeom.height()/2+10;
frame->setScrollBarValue(Qt::Horizontal, pixelsToScrolRight);
frame->setScrollBarValue(Qt::Vertical, pixelsToScrolDown);
QPoint pointToClick(elX-pixelsToScrolRight, elY-pixelsToScrolDown);
QMouseEvent pressEvent(QMouseEvent::MouseButtonPress,
pointToClick, Qt::LeftButton, Qt::LeftButton,
Qt::NoModifier);
QCoreApplication::sendEvent(web, &pressEvent);
QMouseEvent releaseEvent(QMouseEvent::MouseButtonRelease,
pointToClick, Qt::LeftButton, Qt::LeftButton,
Qt::NoModifier);
QCoreApplication::sendEvent(web, &releaseEvent);
QCoreApplication::processEvents();
}
void log(Script*);
bool _log;
protected:
QString _result;
private:
int _line;
QString _testsuite;
QString _targetdir;
};
class Empty: public Command {
public:
QString tag() const {
return "";
}
QString description() const {
return
""
"\n\n"
"Empty lines are allowed";
}
QString command() const {
return "";
}
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
std::shared_ptr<Empty> cmd(new Empty());
return cmd;
}
bool execute(Script*, QWebFrame*) {
return true;
}
};
class Comment: public Command {
public:
Comment(QString line): _line(line) {}
QString tag() const {
return "#";
}
QString description() const {
return
"# comment"
"\n\n"
"Comments are lines that start with #";
}
QString command() const {
return _line;
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Comment> cmd(new Comment(args));
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
return true;
}
private:
QString _line;
};
class Screenshot: public Command {
public:
static QString sourceHtml(int line, QString target, QString base,
QString name, QWebFrame* frame) {
if (!QDir(target).exists() && !QDir().mkpath(target))
throw DirectoryCannotBeCreated(target);
QCoreApplication::processEvents();
QString filename(target+QDir::separator()+
QString("%1-%2-%3.html")
.arg(base)
.arg(line, 4, 10, QChar('0')).arg(name));
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
throw CannotWriteSouceHTML(filename);
QTextStream out(&file);
out<<frame->toHtml();
if (out.status()!=QTextStream::Ok) throw CannotWriteSouceHTML(filename);
return QDir(filename).absolutePath();
}
static QString screenshot(int line, QString target, QString base,
QString name, QWebFrame* frame) {
bool wasShown(frame->page()->view()->isVisible());
frame->page()->view()->show();
QCoreApplication::processEvents();
QImage image(frame->page()->view()->size(), QImage::Format_RGB32);
QPainter painter(&image);
frame->render(&painter);
painter.end();
if (!wasShown) frame->page()->view()->hide();
if (!QDir(target).exists() && !QDir().mkpath(target))
throw DirectoryCannotBeCreated(target);
QCoreApplication::processEvents();
QString filename(target+QDir::separator()+
QString("%1-%2-%3.png")
.arg(base)
.arg(line, 4, 10, QChar('0')).arg(name));
if (!image.save(filename)) throw CannotWriteScreenshot(filename);
return QDir(filename).absolutePath();
}
QString tag() const {
return "screenshot";
}
QString description() const {
return
"screenshot <filename-base>"
"\n\n"
"Create a PNG screenshot of the actual web page and store it in the "
"file <filename-base>.png. If not already opened, a browser window "
"will pop up to take the screenshot.";
}
QString command() const {
return "screenshot "+_filename;
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Screenshot> cmd(new Screenshot());
cmd->_filename = args;
return cmd;
}
bool execute(Script* script, QWebFrame* frame);
private:
QString _filename;
};
class RunDownload: public QObject {
Q_OBJECT;
public:
RunDownload(QNetworkReply* reply, QString filename):
_reply(reply), _file(filename) {
connect(_reply, SIGNAL(finished()), SLOT(finished()));
connect(_reply, SIGNAL(downloadProgress(qint64, qint64)),
SLOT(downloadProgress(qint64, qint64)));
_file.open(QIODevice::WriteOnly);
}
~RunDownload() {
disconnect(_reply, SIGNAL(finished()), this, SLOT(finished()));
disconnect(_reply, SIGNAL(downloadProgress(qint64, qint64)),
this, SLOT(downloadProgress(qint64, qint64)));
delete _reply;
}
Q_SIGNALS:
void completed(bool netsuccess, bool filesuccess);
private Q_SLOTS:
void finished() {
_file.write(_reply->readAll());
_file.close();
completed(_reply->error()==QNetworkReply::NoError,
_file.error()==QFileDevice::NoError);
delete this;
}
void downloadProgress(qint64 bytes, qint64) {
_file.write(_reply->read(bytes));
}
private:
QNetworkReply* _reply;
QFile _file;
};
/** @mainpage
The WebTester web application testing framework consists of three parts:
-# a gui to create testcases, see @ref webtester
-# a command line tool to run testcases, see @ref webrunner
-# test scripts, see @ref testscript. */
class Script: public QObject {
Q_OBJECT;
Q_SIGNALS:
void logging(QString);
public:
typedef std::pair<QString, QStringList> Signal;
public:
static QString xmlattr(QString attr, bool br = false) {
attr.replace("&", "&amp;")//.replace(" ", "&nbsp;")
.replace("\"", "&quot;");
if (br) attr.replace("\n", "<br/>");
attr.replace("<", "&lt;").replace(">", "&gt;");
return attr;
}
static QString xmlstr(const std::string& attr) {
return xmlstr(QString::fromStdString(attr));
}
static QString xmlstr(QString attr) {
return attr
.replace("&gt;", ">").replace("&lt;", "<")
.replace("<br/>", "\n").replace("&quot;", "\"")
.replace("&nbsp;", " ").replace("&amp;", "&");
}
public:
Script() {
initPrototypes();
}
QString syntax() const {
return
"Script syntax is a text file that consists of list of commands. Each "
"command starts at the begin of a new line. Empty lines are allowed. "
"Lines that start with \"#\" are treated as comments."
"\n\n"
"Note: When a selector is required as parameter, then the selector "
"is a CSS selector that must not contain spaces.";
}
QString commands() const {
QString cmds;
for (auto it(_prototypes.begin()); it!=_prototypes.end(); ++it)
cmds+="\n\n"+it->second->description();
return cmds.trimmed();
}
void reset() {
_script.clear();
while (!_signals.empty()) _signals.pop();
_timer.stop();
_ignores.clear();
_cout.clear();
_cerr.clear();
}
std::shared_ptr<Command> parse(QStringList& in, int linenr) try {
QString line(in.takeFirst().trimmed());
QString cmd(line), args;
int space(line.indexOf(' '));
if (space>0) {
cmd = line.left(space);
args = line.right(line.size()-space-1);
}
Prototypes::const_iterator it(_prototypes.find(cmd));
if (it!=_prototypes.end()) {
std::shared_ptr<Command> command(it->second->parse
(this, args, in, linenr));
command->line(linenr);
return command;
} else {
return unknown(line);
}
} catch (Exception& e) {
e.line(linenr);
throw;
}
void parse(QStringList in) {
for (int linenr(1), oldsize(0);
oldsize=in.size(), in.size();
linenr+=oldsize-in.size())
_script.push_back(parse(in, linenr));
}
void run(QWebFrame* frame, xml::Node& testsuite,
QString targetdir = QString(), bool screenshots = true,
int maxretries = 0) {
_timeout = 20; // defaults to 20s
_ignoreSignalsUntil.clear();
addSignals(frame);
_screenshots = screenshots;
_timer.setSingleShot(true);
int retries(0), back(0);
for (auto cmd(_script.begin()); cmd!=_script.end(); ++cmd) {
xml::Node testcase("testcase");
try {
testcase.attr("classname") =
testsuite.attr("name");
//xmlattr((*cmd)->command(), true).toStdString();
testcase.attr("name") =
xmlattr((*cmd)->tag(), true).toStdString();
if (!_ignores.size() || (*cmd)->tag()=="label") { // not ignored
_timer.start(_timeout*1000);
(*cmd)->testsuite(xmlstr(testsuite.attr("name")));
(*cmd)->targetdir(!targetdir.isEmpty() ? targetdir :
xmlstr(testsuite.attr("name")));
try {
if (!(*cmd)->execute(this, frame)) {
_timer.stop();
if (!back) retries = 0; else --back;
testcase<<(xml::String("system-out") =
xmlattr(_cout).toStdString());
testcase<<(xml::String("system-err") =
xmlattr(_cerr).toStdString());
_cout.clear();
_cerr.clear();
testsuite<<testcase;
break; // test is successfully finished
}
} catch (PossibleRetryLoad& e) {
_timer.stop();
// timeout may happen during load due to bad internet connection
if (screenshots)
try { // take a screenshot on error
QString filename(Screenshot::screenshot
((*cmd)->line(), (*cmd)->targetdir(),
QFileInfo((*cmd)->testsuite()).baseName(),
QString("retry-%1")
.arg((ulong)retries, 2, 10,
QLatin1Char('0')),
frame));
plainlog("[[ATTACHMENT|"+filename+"]]");
} catch (... ) {} // ignore exception in screenshot
if (++retries<=maxretries) { // retry in that case
QUrl url(frame->url());
if ((*cmd)->command()=="expect loadFinished true") {
------cmd;
back += 3;
_ignoreSignalsUntil = "loadStarted";
frame->load(url);
} else if ((*cmd)->command()=="expect loadStarted") {
----cmd;
back += 2;
_ignoreSignalsUntil = "loadStarted";
frame->page()->triggerAction(QWebPage::Stop);
} else if ((*cmd)->command().startsWith("expect urlChanged")) {
QString url2((*cmd)->command());
url2.remove("expect urlChanged");
if (url2.size()) url=url2.trimmed();
----cmd;
back += 2;
_ignoreSignalsUntil = "loadStarted";
frame->load(url);
} else {
throw;
}
} else {
throw;
}
log(QString("WARNING: retry#%1, redo last %2 steps; error: %3")
.arg(retries).arg(back).arg(e.what()));
}
_timer.stop();
if (!back) retries = 0; else --back;
testcase<<(xml::String("system-out") =
xmlattr(_cout).toStdString());
testcase<<(xml::String("system-err") =
xmlattr(_cerr).toStdString());
_cout.clear();
_cerr.clear();
testsuite<<testcase;
}
} catch (Exception& e) {
_timer.stop();
if (!back) retries = 0; else --back;
testcase<<(xml::String("system-out") =
xmlattr(_cout).toStdString());
testcase<<(xml::String("system-err") =
xmlattr(_cerr).toStdString());
_cout.clear();
_cerr.clear();
testsuite<<testcase;
removeSignals(frame);
e.line((*cmd)->line());
if (screenshots)
try { // write html source and take a last screenshot on error
{
QString filename(Screenshot::sourceHtml
((*cmd)->line(), (*cmd)->targetdir(),
QFileInfo((*cmd)->testsuite()).baseName(),
"error", frame));
plainlog("[[ATTACHMENT|"+filename+"]]");
} {
QString filename(Screenshot::screenshot
((*cmd)->line(), (*cmd)->targetdir(),
QFileInfo((*cmd)->testsuite()).baseName(),
"error", frame));
plainlog("[[ATTACHMENT|"+filename+"]]");
}
} catch (... ) {} // ignore exception in screenshot
throw;
}
}
removeSignals(frame);
if (!_signals.empty()) throw UnhandledSignals(_signals);
}
QString& cout() {
return _cout;
}
QString& cerr() {
return _cerr;
}
int steps() {
return _script.size();
}
bool screenshots() {
return _screenshots;
}
Signal getSignal() {
while (!_signals.size()) QCoreApplication::processEvents();
Signal res(_signals.front());
_signals.pop();
return res;
}
QTimer& timer() {
return _timer;
}
void ignoreto(const QString& l) {
_ignores.insert(l);
}
void label(const QString& l) {
_ignores.remove(l);
}
void set(QString name, QString value) {
_variables[name] = value;
}
void unset(QString name) {
_variables.remove(name);
}
void timeout(int t) {
_timeout = t;
}
QString replacevars(QString txt) {
for(QMap<QString, QString>::iterator it(_variables.begin());
it!=_variables.end(); ++it)
txt.replace(it.key(), it.value());
return txt;
}
public Q_SLOTS:
void log(QString text) {
text = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss ")+text;
logging(text);
std::cout<<text<<std::endl<<std::flush;
_cout += text + "\n";
}
void plainlog(QString text) {
logging(text);
std::cout<<text<<std::endl<<std::flush;
_cout += text + "\n";
}
private:
std::shared_ptr<Command> unknown(QString line) {
if (!line.size()) return std::shared_ptr<Command>(new Empty());
if (line[0]=='#') return std::shared_ptr<Command>(new Comment(line));
throw UnknownCommand(line); // error
}
void addSignals(QWebFrame* frame) {
connect(dynamic_cast<NetworkAccessManager*>
(frame->page()->networkAccessManager()),
SIGNAL(log(QString)),
SLOT(log(QString)));
connect(frame, SIGNAL(contentsSizeChanged(const QSize&)),
SLOT(contentsSizeChanged(const QSize&)));
connect(frame, SIGNAL(iconChanged()),
SLOT(iconChanged()));
connect(frame, SIGNAL(initialLayoutCompleted()),
SLOT(initialLayoutCompleted()));
connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
SLOT(javaScriptWindowObjectCleared()));
connect(frame, SIGNAL(loadFinished(bool)),
SLOT(loadFinished(bool)));
connect(frame, SIGNAL(loadStarted()),
SLOT(loadStarted()));
connect(frame, SIGNAL(titleChanged(const QString&)),
SLOT(titleChanged(const QString&)));
connect(frame, SIGNAL(urlChanged(const QUrl&)),
SLOT(urlChanged(const QUrl&)));
connect(&_timer, SIGNAL(timeout()), SLOT(timeout()));
}
void removeSignals(QWebFrame* frame) {
disconnect(dynamic_cast<NetworkAccessManager*>
(frame->page()->networkAccessManager()),
SIGNAL(log(QString)),
this, SLOT(log(QString)));
disconnect(frame, SIGNAL(contentsSizeChanged(const QSize&)),
this, SLOT(contentsSizeChanged(const QSize&)));
disconnect(frame, SIGNAL(iconChanged()),
this, SLOT(iconChanged()));
disconnect(frame, SIGNAL(initialLayoutCompleted()),
this, SLOT(initialLayoutCompleted()));
disconnect(frame, SIGNAL(javaScriptWindowObjectCleared()),
this, SLOT(javaScriptWindowObjectCleared()));
disconnect(frame, SIGNAL(loadFinished(bool)),
this, SLOT(loadFinished(bool)));
disconnect(frame, SIGNAL(loadStarted()),
this, SLOT(loadStarted()));
disconnect(frame, SIGNAL(titleChanged(const QString&)),
this, SLOT(titleChanged(const QString&)));
disconnect(frame, SIGNAL(urlChanged(const QUrl&)),
this, SLOT(urlChanged(const QUrl&)));
disconnect(frame, SIGNAL(urlChanged(const QUrl&)),
this, SLOT(urlChanged(const QUrl&)));
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(timeout()));
}
void initPrototypes();
void add(Command* c) {
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
}
private Q_SLOTS:
void contentsSizeChanged(const QSize&) {
}
void iconChanged() {
}
void initialLayoutCompleted() {
}
void javaScriptWindowObjectCleared() {
}
void loadFinished(bool ok) {
QString sig(ok?"true":"false");
if (_ignoreSignalsUntil.size() &&
_ignoreSignalsUntil != "loadFinished "+sig) {
log("warning: ignored loadFinished, waiting for "+_ignoreSignalsUntil);
return;
}
_ignoreSignalsUntil.clear();
log(".... signal");
log("received loadFinished "+QString(ok?"true":"false"));
log(".....................");
_signals.push(std::make_pair("loadFinished", QStringList(sig)));
}
void loadStarted() {
if (_ignoreSignalsUntil.size() && _ignoreSignalsUntil != "loadStarted") {
log("warning: ignored loadStarted, waiting for "+_ignoreSignalsUntil);
return;
}
_ignoreSignalsUntil.clear();
log(".... signal");
log("received loadStarted");
log(".....................");
_signals.push(std::make_pair("loadStarted", QStringList()));
}
void frameChanged() {
}
void titleChanged(const QString&) {
//_signals.push(std::make_pair("titleChanged", QStringList(title)));
}
void urlChanged(const QUrl& url) {
if (_ignoreSignalsUntil.size() && _ignoreSignalsUntil != "urlChanged") {
log("warning: ignored urlChanged, waiting for "+_ignoreSignalsUntil);
return;
}
_ignoreSignalsUntil.clear();
log(".... signal");
log("received urlChanged "+url.toString());
log(".....................");
_signals.push(std::make_pair("urlChanged",
QStringList(url.toString())));
}
void timeout() {
throw TimeOut();
}
private:
typedef std::map<QString, std::shared_ptr<Command>> Prototypes;
typedef std::vector<std::shared_ptr<Command>> Commands;
Prototypes _prototypes;
Commands _script;
std::queue<Signal> _signals;
QTimer _timer;
QSet<QString> _ignores;
QString _cout;
QString _cerr;
bool _screenshots;
QString _ignoreSignalsUntil;
QMap<QString, QString> _variables;
int _timeout;
};
class Do: public Command {
public:
QString tag() const {
return "do";
}
QString description() const {
return
"do <selector>\n <javascript-line1>\n <javascript-line2>"
"\n\n"
"Execute JavaScript on a CSS selected object. The object is the first "
"object in the DOM tree that matches the given CSS selector. You can "
"refere to the selected object within the scripy by \"this\". The "
"JavaScript code is on the following lines and at least intended by "
"one space";
}
QString command() const {
return "do "+_selector+_javascript;
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList& in, int) {
std::shared_ptr<Do> cmd(new Do());
cmd->_selector = args;
while (in.size() && in[0].size() && in[0][0]==' ')
cmd->_javascript += "\n"+in.takeFirst();
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
QWebElement element(find(frame, _selector, log));
if (element.isNull()) throw ElementNotFound(_selector);
_result =
element.evaluateJavaScript(script->replacevars(_javascript)).toString();
return true;
}
private:
QString _selector;
QString _javascript;
};
class Load: public Command {
public:
QString tag() const {
return "load";
}
QString description() const {
return
"load <url>"
"\n\n"
"Load an URL, the URL is given as parameter in full syntax.";
}
QString command() const {
return "load "+_url;
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Load> cmd(new Load());
cmd->_url = args;
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
frame->load(script->replacevars(_url));
return true;
}
private:
QString _url;
};
class Expect: public Command {
public:
QString tag() const {
return "expect";
}
QString description() const {
return
"expect <signal> [<parameter>]"
"\n\n"
"Expect a signal. Signals are emitted by webkit and may contain "
"parameter. If a parameter is given in the script, then the parameter "
"must match exactly. If no parameter is given, then the signal must "
"be emitted, but the parameters of the signal are not checked."
"\n\n"
"Known signals and parameters are:\n"
" - loadFinished <bool: \"true\" if ok, \"false\" on error>\n"
" - loadStarted\n"
" - urlChanged <url>";
}
QString command() const {
return "expect "+_signal+(_args.size()?" "+_args.join(' '):QString());
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Expect> cmd(new Expect());
cmd->_args = args.split(" ");
cmd->_signal = cmd->_args.takeFirst();
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
QString signal(_signal);
QStringList args;
Q_FOREACH(QString arg, _args) args.push_back(script->replacevars(arg));
Script::Signal lastsignal(script->getSignal());
QStringList lastargs;
Q_FOREACH(QString arg, lastsignal.second)
lastargs.push_back(script->replacevars(arg));
if (lastsignal.first!=signal || (args.size() && args!=lastargs))
throw WrongSignal(signal, args, lastsignal);
return true;
}
private:
QString _signal;
QStringList _args;
};
class Open: public Command {
public:
QString tag() const {
return "open";
}
QString description() const {
return
"open"
"\n\n"
"Open the browser window, so you can follow the test steps visually.";
}
QString command() const {
return "open";
}
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
std::shared_ptr<Open> cmd(new Open());
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
frame->page()->view()->show();
return true;
}
};
class Sleep: public Command {
public:
QString tag() const {
return "sleep";
}
QString description() const {
return
"sleep <seconds>"
"\n\n"
"Sleep for a certain amount of seconds. This helps, if you must wait "
"for some javascript actions, i.e. AJAX or slow pages, and the "
"excpeted signals are not sufficient.";
}
QString command() const {
return "sleep "+_time;
}
std::shared_ptr<Command> parse(Script*, QString time, QStringList&, int) {
std::shared_ptr<Sleep> cmd(new Sleep());
cmd->_time = "10"; // default: 10s
if (time.size()) cmd->_time = time;
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
script->timer().stop();
bool ok;
unsigned int time(script->replacevars(_time).toUInt(&ok));
if (!ok)
throw BadArgument(script->replacevars(_time)
+" should be a number of seconds");
sleep(time);
return true;
}
private:
QString _time;
};
class Exit: public Command {
public:
QString tag() const {
return "exit";
}
QString description() const {
return
"exit"
"\n\n"
"Successfully terminate script immediately. The following commands "
"are not executed. This helps when you debug your scripts and you "
"want the script stop at a certain point for investigations.";
}
QString command() const {
return "exit";
}
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
std::shared_ptr<Exit> cmd(new Exit());
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
return false;
}
};
class IgnoreTo: public Command {
public:
QString tag() const {
return "ignoreto";
}
QString description() const {
return
"ignoreto <label>"
"\n\n"
"Ignore all following commands up to a given label. The following "
"commands are not executed until the given label appears in the "
"script. This helps when you debug your scripts and you "
"want to skip some lines of script code.";
}
QString command() const {
return "ignoreto "+_label;
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<IgnoreTo> cmd(new IgnoreTo());
if (!args.size()) throw BadArgument("ignoreto needs a label");
cmd->_label=args;
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
script->ignoreto(script->replacevars(_label));
return true;
}
private:
QString _label;
};
class Label: public Command {
public:
QString tag() const {
return "label";
}
QString description() const {
return
"label <label>"
"\n\n"
"This marks the label refered by command \"ignoreto\".";
}
QString command() const {
return "label "+_label;
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Label> cmd(new Label());
if (!args.size()) throw BadArgument("label needs a label as parameter");
cmd->_label=args;
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
script->label(script->replacevars(_label));
return true;
}
private:
QString _label;
};
class Upload: public Command {
public:
QString tag() const {
return "upload";
}
QString description() const {
return
"upload <selector> -> <filename>"
"\n\n"
"Presses the specified file upload button and passes a given file "
"name. The command requires a CSS selector followed by a filename. "
"The first object that matches the selector is used.";
}
QString command() const {
return "upload "+_selector+" -> "+_filename;
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Upload> cmd(new Upload());
QStringList allargs(args.split("->"));
if (allargs.size()<2)
throw BadArgument("upload needs a selector folowed by a filename, "
"instead of: \""+args+"\"");
cmd->_selector = allargs.takeFirst().trimmed();
cmd->_filename = allargs.join(" ").trimmed();
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
TestWebPage* page(dynamic_cast<TestWebPage*>(frame->page()));
assert(page);
QString filename(script->replacevars(_filename));
if (!QFileInfo(filename).exists()) {
QStringList files(QFileInfo(filename).dir()
.entryList(QStringList(filename)));
if (files.size()==1) filename=files[0];
}
page->setNextUploadFile(filename);
realMouseClick(frame, script->replacevars(_selector), log);
if (page->uploadPrepared())
throw SetFileUploadFailed(script->replacevars(_selector), filename);
return true;
}
private:
QString _selector;
QString _filename;
};
class Exists: public Command {
public:
QString tag() const {
return "exists";
}
QString description() const {
return
"exists <selector> -> <text>"
"\n\n"
"Assert that a certain text exists in the selected object, or if no "
"text is given, assert that the specified object exists. The object "
"is given by a CSS selector. All matching objects are search for the "
"text.";
}
QString command() const {
return "exists "+_selector+(_text.size()?" -> "+_text:QString());
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Exists> cmd(new Exists());
QStringList allargs(args.split("->"));
if (allargs.size()<2) {
cmd->_selector = args;
} else {
cmd->_selector = allargs.takeFirst().trimmed();
cmd->_text = allargs.join(" ").trimmed();
}
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
QString selector(script->replacevars(_selector));
QString text(script->replacevars(_text));
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
if (text.isEmpty()) return true; // just find element
if (element.toOuterXml().indexOf(text)!=-1) return true;
}
QWebElement element(find(frame, selector, log));
if (text.isEmpty())
throw AssertionFailed("element not found: "+selector);
else if (element.isNull())
throw AssertionFailed("expected \""+text+"\" in non existing element "
+selector);
else
throw AssertionFailed("not found \""+text+"\" in \""
+element.toOuterXml()+"\" on "+selector);
return true; // never reached due to throw above
}
private:
QString _selector;
QString _text;
};
class Not: public Command {
public:
QString tag() const {
return "not";
}
QString description() const {
return
"not <selector> -> <text>"
"\n\n"
"Assert that a certain text does not exists in the selected object, "
"or if no text is given, assert that the specified object does not "
"exists. The object is given by a CSS selector. All matching objects "
"are search for the text.";
}
QString command() const {
return "not "+_selector+(_text.size()?" -> "+_text:QString());
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Not> cmd(new Not());
QStringList allargs(args.split("->"));
if (allargs.size()<2) {
cmd->_selector = args;
} else {
cmd->_selector = allargs.takeFirst().trimmed();
cmd->_text = allargs.join(" ").trimmed();
}
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
QString selector(script->replacevars(_selector));
QString text(script->replacevars(_text));
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
if (text.isEmpty())
throw AssertionFailed("element must not exists: "+selector);
if (element.toOuterXml().indexOf(text)!=-1)
throw AssertionFailed("\""+text+"\" must not be in \""
+element.toInnerXml()+"\" on "+selector);
}
return true;
}
private:
QString _selector;
QString _text;
};
class Execute: public Command {
public:
QString tag() const {
return "execute";
}
QString description() const {
return
"execute <command>\n <line1>\n <line2>\n <...>"
"\n\n"
"Execute <command>. The command can have space separated arguments. "
"Following lines that are intended by at least "
"one space are streamed into the command. This way, you can e.g. "
"execute a bash script to check a file you downloaded from a page.";
}
QString command() const {
QStringList script(_script);
script.replaceInStrings(QRegExp("^"), " ");
return "execute "+_command
+(_args.size()?" "+_args.join(' '):QString())
+(script.size()?"\n"+script.join("\n"):QString());
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList& in, int) {
std::shared_ptr<Execute> cmd(new Execute());
cmd->_args = args.split(' ');
cmd->_command = cmd->_args.takeFirst();
int pos(-1);
while (in.size() && in[0].size() && in[0][0]==' ') {
if (pos<0) pos=in[0].toStdString().find_first_not_of(' ');
cmd->_script += in.takeFirst().mid(pos);
}
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
script->timer().stop();
QString command(script->replacevars(_command));
QStringList args;
QString scripttxt(script->replacevars(_script.join("\n")));
Q_FOREACH(QString arg, _args) args.push_back(script->replacevars(arg));
QProcess exec;
exec.setProcessChannelMode(QProcess::MergedChannels);
exec.start(command, args);
if (!exec.waitForStarted())
throw CannotStartScript(command, args, scripttxt);
if (scripttxt.size()) {
if (exec.write(scripttxt.toUtf8())!=scripttxt.toUtf8().size() ||
!exec.waitForBytesWritten(60000))
throw CannotLoadScript(command, args, scripttxt);
}
exec.closeWriteChannel();
if (!exec.waitForFinished(60000) && exec.state()!=QProcess::NotRunning)
throw ScriptNotFinished(command, args, scripttxt);
QString stdout(exec.readAllStandardOutput());
QString stderr(exec.readAllStandardError());
_result = stdout;
script->log(stdout);
if (exec.exitCode()!=0 || exec.exitStatus()!=QProcess::NormalExit)
throw ScriptExecutionFailed(command, args, scripttxt,
exec.exitCode(), stdout, stderr);
return true;
}
private:
QString _command;
QStringList _args;
QStringList _script;
};
class Download: public Command {
Q_OBJECT;
public:
QString tag() const {
return "download";
}
QString description() const {
return
"download <filename>"
"<command-to-start-download>"
"\n\n"
"Set download file before loading a download link or clicking on a "
"download button. The next line must be exactly one command that "
"initiates the download.";
}
QString command() const {
return "download"+(_filename.size()?" "+_filename:QString())+"\n"
+_next->command();
}
std::shared_ptr<Command> parse(Script* script, QString args,
QStringList& in, int line) {
std::shared_ptr<Download> cmd(new Download());
cmd->_filename = args.trimmed();
cmd->_next = script->parse(in, line+1);
cmd->_next->log(false); // suppress logging of subcommand
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
frame->page()->setForwardUnsupportedContent(true);
connect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
this, SLOT(unsupportedContent(QNetworkReply*)));
try {
bool res(_next->execute(script, frame)); // start download
script->timer().stop(); // no timeout during download
for (_done = false; !_done;) // wait for download finish
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
log.log("download terminated "+
QString(_netsuccess&&_filesuccess?"successfully":"with error"));
if (!_netsuccess) throw DownloadFailed(_realfilename);
if (!_filesuccess) throw WriteFileFailed(_realfilename);
log.plainlog("[[ATTACHMENT|"+QDir(_realfilename).absolutePath()+"]]");
disconnect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
this, SLOT(unsupportedContent(QNetworkReply*)));
return res;
} catch (...) {
disconnect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
this, SLOT(unsupportedContent(QNetworkReply*)));
throw;
}
}
private Q_SLOTS:
void completed(bool netsuccess, bool filesuccess) {
_done = true;
_netsuccess = netsuccess;
_filesuccess = filesuccess;
}
void unsupportedContent(QNetworkReply* reply) {
_realfilename = reply->url().toString().split('/').last();
if (_filename.size())
_realfilename = _filename;
else if (reply->header(QNetworkRequest::ContentDispositionHeader)
.isValid()) {
QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
.toString());
if (part.contains(QRegExp("attachment; *filename="))) {
part.replace(QRegExp(".*attachment; *filename="), "");
if (part.size()) _realfilename = part;
}
}
connect(new RunDownload(reply, _realfilename),
SIGNAL(completed(bool, bool)), SLOT(completed(bool, bool)));
}
private:
QString _filename;
QString _realfilename;
std::shared_ptr<Command> _next;
bool _done, _netsuccess, _filesuccess;
};
class Click: public Command {
public:
QString tag() const {
return "click";
}
QString description() const {
return
"click <selector>"
"\n\n"
"Click on the specified element";
}
QString command() const {
return "click "+_selector;
}
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
std::shared_ptr<Click> cmd(new Click());
cmd->_selector = args;
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
realMouseClick(frame, script->replacevars(_selector), log);
return true;
}
private:
QString _selector;
};
class Set: public Command {
public:
QString tag() const {
return "set";
}
QString description() const {
return
"set <variable>=<value>\n"
"set <variable>\n"
" <command>"
"\n\n"
"Sets the value of a variable either to a constant, or to the output"
" of a command. A command should be a command that produces an"
" output, such as «do», which returns the result of JavaScript or"
" «execute», which returns the output of the executed command.";
}
QString command() const {
if (_next)
return "set "+_name+"\n "+_next->command();
else
return "set "+_name+" = "+_value;
}
std::shared_ptr<Command> parse(Script* script, QString args,
QStringList& in, int line) {
std::shared_ptr<Set> cmd(new Set());
cmd->_next = 0;
QStringList allargs(args.split("="));
cmd->_name = allargs.takeFirst().trimmed();
cmd->_value = allargs.join("=").trimmed();
if (!args.contains('=')) {
cmd->_next = script->parse(in, line+1);
cmd->_next->log(false); // suppress logging of subcommand
}
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
if (_next) {
_next->execute(script, frame);
script->set(script->replacevars(_name),
script->replacevars(_next->result()));
} else {
script->set(script->replacevars(_name),
script->replacevars(_value));
}
return true;
}
private:
QString _name;
QString _value;
std::shared_ptr<Command> _next;
};
class UnSet: public Command {
public:
QString tag() const {
return "unset";
}
QString description() const {
return
"unset <variable>"
"\n\n"
"Undo the setting of a variable. The opposite of «set».";
}
QString command() const {
return "unset "+_name;
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList&, int) {
std::shared_ptr<UnSet> cmd(new UnSet());
cmd->_name = args;
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
script->unset(_name);
return true;
}
private:
QString _name;
};
class Timeout: public Command {
public:
QString tag() const {
return "timeout";
}
QString description() const {
return
"timeout <seconds>"
"\n\n"
"Set the timeout in seconds (defaults to 10).";
}
QString command() const {
return "timeout "+_timeout;
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList&, int) {
std::shared_ptr<Timeout> cmd(new Timeout());
cmd->_timeout = args;
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
bool ok;
int timeout(script->replacevars(_timeout).toInt(&ok));
if (!ok) throw BadArgument(script->replacevars(_timeout)
+" should be a number of seconds");
script->timeout(timeout);
return true;
}
private:
QString _timeout;
};
/* Template:
class : public Command {
public:
QString tag() const {
return "";
}
QString description() const {
return
""
"\n\n"
"";
}
QString command() const {
return "";
}
std::shared_ptr<Command> parse(Script*, QString args,
QStringList& in, int) {
std::shared_ptr<> cmd(new ());
return cmd;
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
return true;
}
};
*/
inline bool Screenshot::execute(Script* script, QWebFrame* frame) {
if (!script->screenshots()) return true;
Logger log(this, script);
QString filename(screenshot(line(), targetdir(),
QFileInfo(testsuite()).baseName(),
_filename, frame));
log.plainlog("[[ATTACHMENT|"+filename+"]]");
return true;
}
inline Logger::Logger(Command* command, Script* script):
_command(command), _script(script) {
if (_command->log()) {
_script->log("---- line: "+QString::number(_command->line()));
_script->log(_command->command());
}
}
inline void Logger::log(QString txt) {
if (_command->log())
_script->log(txt);
}
inline void Logger::plainlog(QString txt) {
_script->plainlog(txt);
}
inline Logger::~Logger() {
if (_command->log())
_script->log("---------------------");
}
inline void Script::initPrototypes() {
add(new Do);
add(new Load);
add(new Expect);
add(new Screenshot);
add(new Open);
add(new Sleep);
add(new Exit);
add(new IgnoreTo);
add(new Label);
add(new Upload);
add(new Exists);
add(new Not);
add(new Execute);
add(new Download);
add(new Click);
add(new Set);
add(new UnSet);
add(new Timeout);
}
#endif

@ -0,0 +1,202 @@
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef EXCEPTIONS_HXX
#define EXCEPTIONS_HXX
#include <stdexcept>
#include <queue>
#include <QDir>
class Exception: public std::exception {
public:
Exception(QString w): _what(w) {}
~Exception() throw() {}
const char* what() const throw() {
return _what.toStdString().c_str();
}
void line(int linenr) {
if (linenr>0) _what="line "+QString::number(linenr)+": "+_what;
}
protected:
QString _what;
};
class ParseError: public Exception {
public:
ParseError(QString w): Exception("parse error: "+w) {}
};
class UnknownCommand: public ParseError {
public:
UnknownCommand(QString line): ParseError("unknown command: \""+line+"\"") {}
};
class BadArgument: public ParseError {
public:
BadArgument(QString arg): ParseError("bad argument: "+arg) {}
};
class TestFailed: public Exception {
public:
TestFailed(QString why): Exception("Test Failed: "+why) {}
};
class PossibleRetryLoad: public TestFailed {
public:
PossibleRetryLoad(QString txt): TestFailed(txt) {}
};
class WrongSignal: public PossibleRetryLoad {
public:
WrongSignal(QString signal, QStringList args,
std::pair<QString, QStringList> received):
PossibleRetryLoad
("expected: \""+signal+" "+args.join(' ')+"\"; "
"received: \""+received.first+" "+received.second.join(' ')+"\"") {
}
};
class UnhandledSignals: public TestFailed {
public:
typedef std::pair<QString, QStringList> Signal;
UnhandledSignals(std::queue<Signal> sigs):
TestFailed("unhandled signals:") {
while (!sigs.empty()) {
Signal res(sigs.front());
_what += "\n"+res.first+" "+res.second.join(' ');
sigs.pop();
}
}
};
class TimeOut: public PossibleRetryLoad {
public:
TimeOut(): PossibleRetryLoad("command timeout") {}
};
class ElementNotFound: public TestFailed {
public:
ElementNotFound(QString selector):
TestFailed("element not found: "+selector) {}
};
class DirectoryCannotBeCreated: public TestFailed {
public:
DirectoryCannotBeCreated(QString name):
TestFailed("cannot create directory: "+name) {}
};
class CannotWriteScreenshot: public TestFailed {
public:
CannotWriteScreenshot(QString name):
TestFailed("cannot write screenshot: "+name) {}
};
class CannotWriteSouceHTML: public TestFailed {
public:
CannotWriteSouceHTML(QString name):
TestFailed("cannot write html source code: "+name) {}
};
class FileNotFound: public TestFailed {
public:
FileNotFound(QString arg): TestFailed("file not found: "+arg) {}
};
class NotUnattended: public TestFailed {
public:
NotUnattended(): TestFailed("web page is not in unattended test mode") {}
};
class LastFileNotUploaded: public TestFailed {
public:
LastFileNotUploaded(QString arg):
TestFailed("last specified upload file has not been uploaded: "+arg) {
}
};
class EmptyUploadFile: public TestFailed {
public:
EmptyUploadFile(): TestFailed("specified upload file is empty string") {}
};
class NoUploadFile: public TestFailed {
public:
NoUploadFile(): TestFailed("no upload file specified") {}
};
class SetFileUploadFailed: public TestFailed {
public:
SetFileUploadFailed(QString selector, QString filename):
TestFailed("set file upload failed for selector "+selector
+" and file "+filename) {
}
};
class AssertionFailed: public TestFailed {
public:
AssertionFailed(QString text):
TestFailed("assertion failed: "+text) {
}
};
class DownloadFailed: public TestFailed {
public:
DownloadFailed(QString file):
TestFailed("download failed of file \""+file+"\"") {
}
};
class WriteFileFailed: public TestFailed {
public:
WriteFileFailed(QString file):
TestFailed("write failed of file \""+QDir(file).absolutePath()+"\"") {
}
};
class ScriptFailed: public TestFailed {
public:
ScriptFailed(QString dsc, QString cmd, QStringList args, QString script):
TestFailed(dsc+"; command: "+cmd
+(args.size()?" "+args.join(' '):QString())
+(script.size()?"\n"+script:QString())) {
}
};
class CannotStartScript: public ScriptFailed {
public:
CannotStartScript(QString command, QStringList args, QString script):
ScriptFailed("command not found", command, args, script) {
}
};
class CannotLoadScript: public ScriptFailed {
public:
CannotLoadScript(QString command, QStringList args, QString script):
ScriptFailed("cannot load script data", command, args, script) {
}
};
class ScriptNotFinished: public ScriptFailed {
public:
ScriptNotFinished(QString command, QStringList args, QString script):
ScriptFailed("command hangs, timeout", command, args, script) {
}
};
class ScriptExecutionFailed: public ScriptFailed {
public:
ScriptExecutionFailed(QString command, QStringList args, QString script,
int code, QString stdout, QString stderr):
ScriptFailed("failed with exit code "+QString::number(code)
+(stdout.size()?"; stdout=\""+stdout+"\"":"")
+(stderr.size()?"; stderr=\""+stderr+"\"":""),
command, args, script) {
}
};
#endif

@ -0,0 +1,212 @@
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef __NETWORKACCESS_MANAGER_HXX__
#define __NETWORKACCESS_MANAGER_HXX__
#include <QNetworkAccessManager>
#include <QNetworkConfiguration>
#include <QNetworkProxy>
class NetworkAccessManager: public QNetworkAccessManager {
Q_OBJECT;
public:
NetworkAccessManager(QObject* parent = 0): QNetworkAccessManager(parent) {
// log(__PRETTY_FUNCTION__);
connect(this,
SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
SLOT(authenticationRequiredLog(QNetworkReply*, QAuthenticator*)));
connect(this,
SIGNAL(encrypted(QNetworkReply*)),
SLOT(encryptedLog(QNetworkReply*)));
connect(this,
SIGNAL(finished(QNetworkReply*)),
SLOT(finishedLog(QNetworkReply*)));
connect(this,
SIGNAL(networkAccessibleChanged
(QNetworkAccessManager::NetworkAccessibility)),
SLOT(networkAccessibleChangedLog
(QNetworkAccessManager::NetworkAccessibility)));
connect(this,
SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&,
QAuthenticator*)),
SLOT(proxyAuthenticationRequiredLog(const QNetworkProxy&,
QAuthenticator*)));
connect(this,
SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)),
SLOT(sslErrorsLog(QNetworkReply*, const QList<QSslError>&)));
}
virtual ~NetworkAccessManager() {
// log(__PRETTY_FUNCTION__);
}
QNetworkConfiguration activeConfiguration() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::activeConfiguration();
}
QAbstractNetworkCache* cache() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::cache();
}
void clearAccessCache() {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::clearAccessCache();
}
QNetworkConfiguration configuration() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::configuration();
}
void connectToHost(const QString& hostName, quint16 port = 80) {
// log(__PRETTY_FUNCTION__);
QNetworkAccessManager::connectToHost(hostName, port);
}
void connectToHostEncrypted(const QString& hostName, quint16 port = 443,
const QSslConfiguration& sslConfiguration
= QSslConfiguration::defaultConfiguration()) {
// log(__PRETTY_FUNCTION__);
QNetworkAccessManager::connectToHostEncrypted(hostName, port,
sslConfiguration);
}
QNetworkCookieJar* cookieJar() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::cookieJar();
}
QNetworkReply* deleteResource(const QNetworkRequest & request) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::deleteResource(request);
}
QNetworkReply* get(const QNetworkRequest& request) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::get(request);
}
QNetworkReply* head(const QNetworkRequest& request) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::head(request);
}
NetworkAccessibility networkAccessible() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::networkAccessible();
}
QNetworkReply* post(const QNetworkRequest & request, QIODevice * data) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::post(request, data);
}
QNetworkReply* post(const QNetworkRequest& request,
const QByteArray& data) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::post(request, data);
}
QNetworkReply* post(const QNetworkRequest& request,
QHttpMultiPart* multiPart) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::post(request, multiPart);
}
QNetworkProxy proxy() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::proxy();
}
QNetworkProxyFactory* proxyFactory() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::proxyFactory();
}
QNetworkReply* put(const QNetworkRequest& request, QIODevice* data) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::put(request, data);
}
QNetworkReply* put(const QNetworkRequest& request,
QHttpMultiPart* multiPart) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::put(request, multiPart);
}
QNetworkReply* put(const QNetworkRequest& request, const QByteArray& data) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::put(request, data);
}
QNetworkReply* sendCustomRequest(const QNetworkRequest& request,
const QByteArray& verb,
QIODevice* data = 0) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::sendCustomRequest(request, verb, data);
}
void setCache(QAbstractNetworkCache* cache) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::setCache(cache);
}
void setConfiguration(const QNetworkConfiguration& config) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::setConfiguration(config);
}
void setCookieJar(QNetworkCookieJar* cookieJar) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::setCookieJar(cookieJar);
}
void setNetworkAccessible(NetworkAccessibility accessible) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::setNetworkAccessible(accessible);
}
void setProxy(const QNetworkProxy& proxy) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::setProxy(proxy);
}
void setProxyFactory(QNetworkProxyFactory* factory) {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::setProxyFactory(factory);
}
QStringList supportedSchemes() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::supportedSchemes();
}
Q_SIGNALS:
void authenticationRequired(QNetworkReply*, QAuthenticator*);
void encrypted(QNetworkReply*);
void finished(QNetworkReply*);
void networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility);
void proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*);
void sslErrors(QNetworkReply*, const QList<QSslError>&);
void log(QString) const;
protected:
virtual QNetworkReply* createRequest(Operation op,
const QNetworkRequest& req,
QIODevice* outgoingData = 0) {
// log(__PRETTY_FUNCTION__);
switch (op) {
case QNetworkAccessManager::HeadOperation: break;
case QNetworkAccessManager::GetOperation: break;
case QNetworkAccessManager::PutOperation: break;
case QNetworkAccessManager::PostOperation: break;
case QNetworkAccessManager::DeleteOperation: break;
case QNetworkAccessManager::CustomOperation: break;
case QNetworkAccessManager::UnknownOperation: break;
}
return QNetworkAccessManager::createRequest(op, req, outgoingData);
}
protected Q_SLOTS:
QStringList supportedSchemesImplementation() const {
// log(__PRETTY_FUNCTION__);
return QNetworkAccessManager::supportedSchemesImplementation();
}
private Q_SLOTS:
void authenticationRequiredLog(QNetworkReply*, QAuthenticator*) {
// log(__PRETTY_FUNCTION__);
}
void encryptedLog(QNetworkReply*) {
// log(__PRETTY_FUNCTION__);
}
void finishedLog(QNetworkReply*) {
// log(__PRETTY_FUNCTION__);
}
void networkAccessibleChangedLog
(QNetworkAccessManager::NetworkAccessibility) {
// log(__PRETTY_FUNCTION__);
}
void proxyAuthenticationRequiredLog(const QNetworkProxy&, QAuthenticator*) {
// log(__PRETTY_FUNCTION__);
}
void sslErrorsLog(QNetworkReply*, const QList<QSslError>&) {
// log(__PRETTY_FUNCTION__);
}
};
#endif

@ -0,0 +1,473 @@
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef TESTGUI_HXX
#define TESTGUI_HXX
#include <webpage.hxx>
#include <commands.hxx>
#include <QMainWindow>
#include <QSettings>
#include <QWebFrame>
#include <QWebElement>
#include <QFileDialog>
#include <QFile>
#include <QMessageBox>
#include <ui_testgui.h>
#include <stdexcept>
#include <QNetworkReply>
class TestGUI: public QMainWindow, protected Ui::TestGUI {
Q_OBJECT;
public:
explicit TestGUI(QWidget *parent = 0, QString url = QString()):
QMainWindow(parent),
_typing(false),
_inEventFilter(false) {
setupUi(this);
QSettings settings("mrw", "webtester");
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowstate").toByteArray());
if (!url.isEmpty()) {
_url->setText(url);
}
TestWebPage* page(new TestWebPage(_web));
_web->setPage(page);
_web->installEventFilter(this); // track mouse and keyboard
page->setForwardUnsupportedContent(true);
connect(page, SIGNAL(uploadFile(QString)), SLOT(uploadFile(QString)));
connect(page, SIGNAL(unsupportedContent(QNetworkReply*)),
SLOT(unsupportedContent(QNetworkReply*)));
connect(page, SIGNAL(downloadRequested(const QNetworkRequest&)),
SLOT(downloadRequested(const QNetworkRequest&)));
}
virtual ~TestGUI() {}
public Q_SLOTS:
void on__load_clicked() {
enterText(true);
if (_record->isChecked())
_testscript->appendPlainText("load "+_url->text());
_web->load(_url->text());
}
void on__abort_clicked() {
enterText(true);
_web->stop();
}
void on__actionOpen_triggered() {
QString name(QFileDialog::getOpenFileName(this, tr("Open Test Script")));
if (name.isEmpty()) return;
on__actionRevertToSaved_triggered(name);
}
void on__actionRevertToSaved_triggered() {
on__actionRevertToSaved_triggered(_filename);
}
void on__actionRevertToSaved_triggered(QString name) {
QFile file(name);
try {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
throw std::runtime_error("file open failed");
_testscript->setPlainText(QString::fromUtf8(file.readAll()));
if (file.error()!=QFileDevice::NoError)
throw std::runtime_error("file read failed");
_filename = name;
_actionSave->setEnabled(true);
_actionRevertToSaved->setEnabled(true);
} catch(const std::exception& x) {
QMessageBox::critical(this, tr("Open Failed"),
tr("Reading test script failed, %2. "
"Cannot read test script from file %1.")
.arg(name).arg(x.what()));
}
}
void on__actionSaveAs_triggered() {
QString name(QFileDialog::getSaveFileName(this, tr("Save Test Script")));
if (name.isEmpty()) return;
_filename = name;
on__actionSave_triggered();
}
void on__actionSave_triggered() {
QFile file(_filename);
try {
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
throw std::runtime_error("file open failed");
QTextStream out(&file);
out<<_testscript->toPlainText();
if (out.status()!=QTextStream::Ok)
throw std::runtime_error(std::string("file write failed (")
+char(out.status()+48)+")");
_actionSave->setEnabled(true);
_actionRevertToSaved->setEnabled(true);
} catch(const std::exception& x) {
QMessageBox::critical(this, tr("Save Failed"),
tr("Saving test script failed, %2. "
"Cannot write test script to file %1.")
.arg(_filename).arg(x.what()));
}
}
void on__actionClear_triggered() {
_testscript->clear();
_filename.clear();
_actionSave->setEnabled(false);
_actionRevertToSaved->setEnabled(false);
}
void on__run_clicked() {
bool oldRecordState(_record->isChecked());
_run->setEnabled(false);
try {
xml::Node testsuites("testsuites");
xml::Node testsuite("testsuite");
testsuite.attr("name") = "on-the-fly";
testsuite.attr("timestamp") =
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
xml::Node testcase("testcase");
testcase.attr("classname") = "testsuite-preparation";
QString text(_testscript->textCursor().selectedText());
if (text.isEmpty()) text = _testscript->toPlainText();
Script script;
connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString)));
script.parse(text.split('\n'));
script.run(_web->page()->mainFrame(), testsuite, QString(), false);
} catch (std::exception &x) {
QMessageBox::critical(this, tr("Script Failed"),
tr("Script failed with message:\n%1")
.arg(x.what()));
}
_run->setEnabled(true);
_record->setChecked(oldRecordState);
}
void on__focused_clicked() {
enterText(true);
QWebElement element(focused());
if (element.isNull()) return;
highlight(element);
_focusedText->setText(selector(element));
}
void on__select_clicked() {
enterText(true);
highlight(_web->page()->mainFrame()->documentElement()
.findFirst(_selector->text()));
}
void on__jsClick_clicked() {
enterText(true);
execute(selector(),
"this.click();");
// "var evObj = document.createEvent('MouseEvents');\n"
// "evObj.initEvent( 'click', true, true );\n"
// "this.dispatchEvent(evObj);");
}
void on__jsValue_clicked() {
enterText(true);
QWebElement element(selected());
execute(selector(element),
"this.value='"+value(element).replace("\n", "\\n")+"';");
}
void on__jsExecute_clicked() {
enterText(true);
execute(selector(), _javascriptCode->toPlainText());
}
void on__web_linkClicked(const QUrl& url) {
enterText(true);
if (_record->isChecked())
_testscript->appendPlainText("load "+url.url());
}
void on__web_loadProgress(int progress) {
enterText(true);
_progress->setValue(progress);
}
void on__web_loadStarted() {
enterText(true);
if (_record->isChecked())
_testscript->appendPlainText("expect loadStarted");
_progress->setValue(0);
_urlStack->setCurrentIndex(PROGRESS_VIEW);
}
void on__web_statusBarMessage(const QString&) {
//std::cout<<"statusBarMessage: "<<text.toStdString()<<std::endl;
}
void on__web_titleChanged(const QString&) {
//std::cout<<"titleChanged: "<<title.toStdString()<<std::endl;
}
void on__web_urlChanged(const QUrl& url) {
enterText(true);
if (_record->isChecked())
_testscript->appendPlainText("expect urlChanged "+url.url());
}
void on__web_selectionChanged() {
_source->setPlainText(_web->hasSelection()
? _web->selectedHtml()
: _web->page()->mainFrame()->toHtml());
}
void on__web_loadFinished(bool ok) {
enterText(true);
if (_record->isChecked())
_testscript->appendPlainText("expect loadFinished "
+QString(ok?"true":"false"));
_urlStack->setCurrentIndex(URL_VIEW);
on__web_selectionChanged();
setLinks();
setForms();
setDom();
}
void on__forms_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
if (!item) return;
_source->setPlainText(item->data(0, Qt::UserRole).toString());
}
void on__dom_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
if (!item) return;
_source->setPlainText(item->data(0, Qt::UserRole).toString());
}
void uploadFile(QString filename) {
enterText(true);
if (_record->isChecked())
_testscript->appendPlainText("upload "+filename);
}
void unsupportedContent(QNetworkReply* reply) {
if (!_record->isChecked()) return;
QString filename(reply->url().toString().split('/').last());
if (reply->header(QNetworkRequest::ContentDispositionHeader).isValid()) {
QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
.toString());
if (part.contains(QRegExp("attachment; *filename="))) {
part.replace(QRegExp(".*attachment; *filename="), "");
if (part.size()) filename = part;
}
}
QString text(_testscript->toPlainText());
int pos1(text.lastIndexOf(QRegExp("^do ")));
int pos2(text.lastIndexOf(QRegExp("^load ")));
text.insert(pos1>pos2?pos1:pos2, "download "+filename);
_testscript->setPlainText(text);
_testscript->moveCursor(QTextCursor::End);
_testscript->ensureCursorVisible();
}
void downloadRequested(const QNetworkRequest&) {
if (_record->isChecked())
_testscript->appendPlainText("download2");
}
void logging(QString txt) {
_log->appendPlainText(txt);
}
protected:
void closeEvent(QCloseEvent* event) {
QSettings settings("mrw", "webtester");
settings.setValue("geometry", saveGeometry());
settings.setValue("windowstate", saveState());
QMainWindow::closeEvent(event);
}
bool eventFilter(QObject*, QEvent* event) {
if (_inEventFilter) return false;
_inEventFilter = true;
enterText();
QWebElement element(focused(dynamic_cast<QMouseEvent*>(event)));
switch (event->type()) {
case QEvent::KeyPress: {
QKeyEvent* k(dynamic_cast<QKeyEvent*>(event));
switch (k->key()) {
case Qt::Key_Tab:
case Qt::Key_Backtab: {
enterText(true);
} break;
case Qt::Key_Backspace: {
_keyStrokes.chop(1);
} break;
case Qt::Key_Shift: break;
case Qt::Key_Enter:
case Qt::Key_Return: {
_keyStrokes += "\\n";
_lastFocused=element;
_typing = true;
} break;
default: {
_keyStrokes += k->text();
_lastFocused=element;
_typing = true;
}
}
} break;
case QEvent::MouseButtonRelease: {
enterText(true);
_lastFocused=element;
if (_record->isChecked() && !element.isNull())
_testscript->appendPlainText("click "+selector(_lastFocused));
} break;
case QEvent::InputMethodQuery:
case QEvent::ToolTipChange:
case QEvent::MouseMove:
case QEvent::UpdateLater:
case QEvent::Paint: break;
default: ;//LOG("Event: "<<event->type());
}
_inEventFilter = false;
return false;
}
private:
void enterText(bool force=false) {
if (!force && (!_typing || _lastFocused==focused())) return;
if (_keyStrokes.size() && !_lastFocused.isNull()) {
store(selector(_lastFocused), "this.value='"
+value(_lastFocused).replace("\n", "\\n")+"';");
}
_lastFocused = QWebElement();
_keyStrokes.clear();
_typing = false;
}
QWebElement selected() {
return _web->page()->mainFrame()->documentElement().findFirst(selector());
}
QString selector() {
if (_takeFocused->isChecked())
return selector(focused());
else if (_takeSelect->isChecked())
return _selector->text();
else
return QString(); // error
}
void highlight(QWebElement element) {
element
.evaluateJavaScript("var selection = window.getSelection();"
"selection.setBaseAndExtent(this, 0, this, 1);");
}
QWebElement focused(QMouseEvent* event = 0) {
Q_FOREACH(QWebElement element,
_web->page()->currentFrame()->findAllElements("*")) {
if (element.hasFocus()) {
return element;
}
}
if (event) { // try to find element using mouse position
QWebFrame* frame(_web->page()->frameAt(event->pos()));
if (frame) return frame->hitTestContent(event->pos()).element();
}
return QWebElement();
}
bool unique(QString selector) {
return _web->page()->mainFrame()->findAllElements(selector).count()==1;
}
QString quote(QString txt) {
if (txt.contains('"')) return "'"+txt+"'";
return '"'+txt+'"';
}
QString selector(const QWebElement& element) {
if (element.isNull()) return QString();
if (element.hasAttribute("id") && unique("#"+element.attribute("id"))) {
return "#"+element.attribute("id");
} else if (element.hasAttribute("name") &&
unique(element.tagName().toLower()
+"[name="+quote(element.attribute("name"))+"]")) {
return element.tagName().toLower()
+"[name="+quote(element.attribute("name"))+"]";
} else {
QString res;
Q_FOREACH(QString attr, element.attributeNames()) {
if (attr=="id")
res = "#"+element.attribute("id")+res;
else if (attr=="class")
Q_FOREACH(QString c, element.attribute(attr).split(' ')) {
if (!c.isEmpty()) res = '.'+c+res;
}
else if (element.attribute(attr).isEmpty())
res+="["+attr+"]";
else
res+="["+attr+"="+quote(element.attribute(attr))+"]";
if (unique(element.tagName().toLower()+res))
return element.tagName().toLower()+res;
}
QString p(selector(element.parent()));
if (unique(p+">"+element.tagName().toLower()+res))
return p+">"+element.tagName().toLower()+res;
QString s(selector(element.previousSibling()));
if (unique(s+"+"+element.tagName().toLower()+res))
return s+"+"+element.tagName().toLower()+res;
if (!p.isEmpty())
return p+">"+element.tagName().toLower()+res;
if (!s.isEmpty())
return s+"+"+element.tagName().toLower()+res;
return element.tagName().toLower()+res;
}
}
QString value(QWebElement element) {
return element.evaluateJavaScript("this.value").toString();
//! @bug Bug in Qt, attribute("value") is always empty
// if (element.hasAttribute("value"))
// return element.attribute("value");
// else
// return element.toPlainText();
}
void store(const QString& selector, QString code) {
if (_record->isChecked())
_testscript->appendPlainText("do "+selector+"\n "
+code.replace("\n", "\\n"));
}
void execute(const QString& selector, const QString& code) {
store(selector, code);
_web->page()->mainFrame()->documentElement().findFirst(selector)
.evaluateJavaScript(code);
}
void setLinks() {
QWebElementCollection links(_web->page()->mainFrame()->documentElement()
.findAll("a"));
_links->setRowCount(links.count());
for (int row(0); row<_links->rowCount(); ++row) {
{
QTableWidgetItem* item(new QTableWidgetItem());
item->setText(links[row].attribute("href"));
_links->setItem(row, 0, item);
} {
QTableWidgetItem* item(new QTableWidgetItem());
item->setText(links[row].hasAttribute("title")
? links[row].attribute("title")
: links[row].toInnerXml());
_links->setItem(row, 1, item);
}
_links->horizontalHeader()->resizeSections(QHeaderView::Stretch);
}
}
void setForms() {
QWebElementCollection forms(_web->page()->mainFrame()->documentElement()
.findAll("form"));
_forms->clear();
Q_FOREACH(const QWebElement &form, forms) {
addDomElement(form, _forms->invisibleRootItem());
}
}
void setDom() {
_dom->clear();
addDomElement(_web->page()->mainFrame()->documentElement(),
_dom->invisibleRootItem());
}
//void addDomChildren(const QWebElement&, QTreeWidgetItem*);
void addDomElement(const QWebElement &element,
QTreeWidgetItem *parent) {
QTreeWidgetItem *item(new QTreeWidgetItem());
item->setText(0, element.tagName());
item->setData(0, Qt::UserRole, element.toOuterXml());
parent->addChild(item);
addDomChildren(element, item);
}
void addDomChildren(const QWebElement &parentElement,
QTreeWidgetItem *parentItem) {
for (QWebElement element = parentElement.firstChild();
!element.isNull();
element = element.nextSibling()) {
addDomElement(element, parentItem);
}
}
private:
enum UrlStack {
URL_VIEW = 0,
PROGRESS_VIEW
};
private:
QString _filename;
QWebElement _lastFocused; // cache for last focussed element
QString _keyStrokes; // collect key strokes
bool _typing; // user is typing
bool _inEventFilter; // actually handling event filter
};
#endif // TESTGUI_HXX

@ -0,0 +1,874 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TestGUI</class>
<widget class="QMainWindow" name="TestGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>888</width>
<height>1180</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QStackedWidget" name="_urlStack">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>URL:</string>
</property>
<property name="buddy">
<cstring>_url</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="_url">
<property name="text">
<string>http://web9t.int.swisssign.net/joomla</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_load">
<property name="text">
<string>load</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QProgressBar" name="_progress">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_abort">
<property name="text">
<string>Abort</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QWebView" name="_web" native="true">
<property name="url" stdset="0">
<url>
<string>about:blank</string>
</url>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>888</width>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menuViews">
<property name="title">
<string>Views</string>
</property>
<addaction name="_actionTestScript"/>
<addaction name="_actionDOMTree"/>
<addaction name="_actionLinks"/>
<addaction name="_actionForms"/>
<addaction name="_actionHTMLSouce"/>
<addaction name="_actionExecuteJavaScript"/>
<addaction name="_actionLog"/>
</widget>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="_actionOpen"/>
<addaction name="_actionSave"/>
<addaction name="_actionSaveAs"/>
<addaction name="separator"/>
<addaction name="_actionRun"/>
<addaction name="_actionRunLine"/>
<addaction name="separator"/>
<addaction name="_actionRevertToSaved"/>
<addaction name="_actionClear"/>
<addaction name="_actionQuit"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuViews"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QDockWidget" name="_domDock">
<property name="windowTitle">
<string>DOM Tree</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_5">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QTreeWidget" name="_dom">
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Element</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="_sourceDock">
<property name="windowTitle">
<string>HTML Source</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_7">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QPlainTextEdit" name="_source"/>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="_linksDock">
<property name="windowTitle">
<string>Links</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_8">
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<widget class="QTableWidget" name="_links">
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>URL</string>
</property>
</column>
<column>
<property name="text">
<string>Description</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="_formsDock">
<property name="windowTitle">
<string>Forms</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_9">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QTreeWidget" name="_forms">
<property name="rootIsDecorated">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="_executeDock">
<property name="windowTitle">
<string>Execute JavaScript on First Selected Item</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_10">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="_jsClick">
<property name="text">
<string>Click</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_jsValue">
<property name="text">
<string>Set Value</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPlainTextEdit" name="_javascriptCode">
<property name="toolTip">
<string>JavaScript Code to Execute</string>
</property>
<property name="statusTip">
<string>JavaScript Code to Execute</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;JavaScript code to execute on the selected element. You can use this to refere to the element.&lt;/p&gt;&lt;p&gt;Example, to click the element:&lt;/p&gt;&lt;pre&gt;
var evObj = document.createEvent('MouseEvents');
evObj.initEvent( 'click', true, true );
this.dispatchEvent(evObj);
&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="plainText">
<string>var evObj = document.createEvent('MouseEvents');
evObj.initEvent( 'click', true, true );
this.dispatchEvent(evObj);</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_jsExecute">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Execute</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="_takeFocused">
<property name="text">
<string>select focused</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="_takeSelect">
<property name="text">
<string>select:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="_focusedText">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="_selector">
<property name="toolTip">
<string>CSS-Selector</string>
</property>
<property name="statusTip">
<string>Enter CSS-Selector</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;JavaScript code below will be applied to the first element found by this CSS selector.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QPushButton" name="_focused">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Focused</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_select">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Test Selector</string>
</property>
<property name="statusTip">
<string>Hilight Selection in Page</string>
</property>
<property name="whatsThis">
<string>Shows you the selection by hilighting it in the web page. This helps you to test before you execute.</string>
</property>
<property name="text">
<string>Select</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
<zorder></zorder>
</widget>
</widget>
<widget class="QDockWidget" name="_scriptDock">
<property name="windowTitle">
<string>Test Script</string>
</property>
<attribute name="dockWidgetArea">
<number>4</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_12">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="3">
<widget class="QPlainTextEdit" name="_testscript"/>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="_record">
<property name="text">
<string>Record</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="_run">
<property name="text">
<string>Run</string>
</property>
</widget>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>82</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="_logDock">
<property name="windowTitle">
<string>Script Run Log</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_2">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QPlainTextEdit" name="_log"/>
</item>
</layout>
</widget>
</widget>
<action name="_actionDOMTree">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>DOM Tree</string>
</property>
</action>
<action name="_actionLinks">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Links</string>
</property>
</action>
<action name="_actionForms">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Forms</string>
</property>
</action>
<action name="_actionHTMLSouce">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>HTML Souce</string>
</property>
</action>
<action name="_actionExecuteJavaScript">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Execute JavaScript</string>
</property>
</action>
<action name="_actionTestScript">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Test Script</string>
</property>
</action>
<action name="_actionOpen">
<property name="text">
<string>Open ...</string>
</property>
</action>
<action name="_actionSaveAs">
<property name="text">
<string>Save As ...</string>
</property>
</action>
<action name="_actionQuit">
<property name="text">
<string>Quit</string>
</property>
</action>
<action name="_actionRun">
<property name="text">
<string>Run</string>
</property>
</action>
<action name="_actionRunLine">
<property name="text">
<string>Run Line</string>
</property>
</action>
<action name="_actionClear">
<property name="text">
<string>Clear</string>
</property>
</action>
<action name="_actionLog">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Log</string>
</property>
</action>
<action name="_actionSave">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save</string>
</property>
</action>
<action name="_actionRevertToSaved">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Revert to saved</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>QWebView</class>
<extends>QWidget</extends>
<header>QtWebKitWidgets/QWebView</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>_url</tabstop>
<tabstop>_load</tabstop>
<tabstop>_web</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>_url</sender>
<signal>returnPressed()</signal>
<receiver>_load</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>493</x>
<y>343</y>
</hint>
<hint type="destinationlabel">
<x>580</x>
<y>344</y>
</hint>
</hints>
</connection>
<connection>
<sender>_selector</sender>
<signal>returnPressed()</signal>
<receiver>_jsExecute</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>785</x>
<y>1144</y>
</hint>
<hint type="destinationlabel">
<x>875</x>
<y>1075</y>
</hint>
</hints>
</connection>
<connection>
<sender>_selector</sender>
<signal>editingFinished()</signal>
<receiver>_select</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>785</x>
<y>1144</y>
</hint>
<hint type="destinationlabel">
<x>874</x>
<y>1144</y>
</hint>
</hints>
</connection>
<connection>
<sender>_actionExecuteJavaScript</sender>
<signal>triggered(bool)</signal>
<receiver>_executeDock</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>630</x>
<y>971</y>
</hint>
</hints>
</connection>
<connection>
<sender>_executeDock</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>_actionTestScript</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>630</x>
<y>971</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>_actionDOMTree</sender>
<signal>triggered(bool)</signal>
<receiver>_domDock</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>748</x>
<y>374</y>
</hint>
</hints>
</connection>
<connection>
<sender>_domDock</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>_actionDOMTree</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>748</x>
<y>374</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>_actionLinks</sender>
<signal>triggered(bool)</signal>
<receiver>_linksDock</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>748</x>
<y>537</y>
</hint>
</hints>
</connection>
<connection>
<sender>_linksDock</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>_actionLinks</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>748</x>
<y>537</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>_actionForms</sender>
<signal>triggered(bool)</signal>
<receiver>_formsDock</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>748</x>
<y>699</y>
</hint>
</hints>
</connection>
<connection>
<sender>_formsDock</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>_actionForms</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>748</x>
<y>699</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>_actionHTMLSouce</sender>
<signal>triggered(bool)</signal>
<receiver>_sourceDock</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>971</y>
</hint>
</hints>
</connection>
<connection>
<sender>_sourceDock</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>_actionHTMLSouce</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>971</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>_actionTestScript</sender>
<signal>triggered(bool)</signal>
<receiver>_scriptDock</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>443</x>
<y>155</y>
</hint>
</hints>
</connection>
<connection>
<sender>_scriptDock</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>_actionTestScript</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>443</x>
<y>155</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>_actionLog</sender>
<signal>triggered(bool)</signal>
<receiver>_logDock</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>842</x>
<y>1004</y>
</hint>
</hints>
</connection>
<connection>
<sender>_logDock</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>_actionLog</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>842</x>
<y>1004</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QWebView" name="_web">
<property name="url">
<url>
<string>about:blank</string>
</url>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QWebView</class>
<extends>QWidget</extends>
<header>QtWebKitWidgets/QWebView</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

@ -0,0 +1,70 @@
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef WEBPAGE_HXX
#define WEBPAGE_HXX
#include <exceptions.hxx>
#include <networkaccessmanager.hxx>
#include <QWebPage>
#include <QWebFrame>
#include <QWidget>
#include <QFileDialog>
#include <QFile>
#include <cassert>
#ifndef LOG
#include <iostream>
#define LOG(X) std::clog<<X<<std::endl;
inline std::ostream& operator<<(std::ostream& o, const QString& s) {
return o<<s.toStdString();
}
#endif
class TestWebPage: public QWebPage {
Q_OBJECT;
public:
TestWebPage(QObject* parent = 0, bool unattended = false):
QWebPage(parent),
_unattended(unattended) {
setNetworkAccessManager(new NetworkAccessManager(this));
}
virtual ~TestWebPage() {
if (!_nextFile.isEmpty() && !std::uncaught_exception())
throw LastFileNotUploaded(_nextFile);
}
void setNextUploadFile(QString nextFile) {
if (!_unattended) throw NotUnattended();
if (!_nextFile.isEmpty()) throw LastFileNotUploaded(_nextFile);
if (nextFile.isEmpty()) throw EmptyUploadFile();
_nextFile = nextFile;
}
bool uploadPrepared() {
return !_nextFile.isEmpty();
}
Q_SIGNALS:
void uploadFile(QString filename);
protected:
virtual QString chooseFile(QWebFrame* frame, const QString&) {
if (_unattended) {
if (_nextFile.isEmpty()) throw NoUploadFile();
if (!QFile(_nextFile).exists()) throw FileNotFound(_nextFile);
QString filename(_nextFile);
_nextFile.clear();
return filename;
} else {
QString filename(QFileDialog::getOpenFileName
(frame->page()->view(), tr("File to Upload")));
if (filename.size()) uploadFile(filename);
return filename;
}
}
private:
bool _unattended;
QString _nextFile;
};
#endif

@ -0,0 +1,178 @@
#include <commands.hxx>
#include <webrunner.hxx>
#include <webpage.hxx>
#include <QApplication>
#include <QFile>
#include <QWebView>
#include <QStringList>
#include <QCommandLineParser>
#include <iostream>
#include <sstream>
#include <fstream>
#include <QDateTime>
#include <xml-cxx/xml.hxx>
#include <mrw/string.hxx>
std::string VERSION("0.9.4");
QString format(QString txt, int indent = 2, int cpl = 60) {
QStringList res;
QStringList lines(txt.split('\n'));
QString ind(indent, ' ');
Q_FOREACH(QString line, lines) {
line.insert(0, ind);
for (int pos(0); line.size()-pos>cpl; ++pos) {
pos=line.lastIndexOf(' ', pos+cpl);
line.remove(pos, 1);
line.insert(pos, "\n"+ind);
}
res+=line;
}
return res.join('\n');
}
QString help(const Script& s) {
std::ostringstream ss;
ss<<"Synopsis"<<std::endl
<<std::endl
<<" webrunner [<OPTIONS>] [<file1> [<file2> [...]]]"<<std::endl
<<std::endl
<<"Script Syntax"<<std::endl
<<std::endl
<<format(s.syntax()).toStdString()<<std::endl
<<std::endl
<<"Script Commands"<<std::endl
<<std::endl
<<format(s.commands()).toStdString()<<std::endl;
return QString::fromStdString(ss.str());
}
int main(int argc, char *argv[]) try {
bool failed(false);
QApplication a(argc, argv);
QWebView p;
p.setPage(new TestWebPage(&p, true));
QCommandLineParser parser;
Script script;
parser.setApplicationDescription(help(script));
parser.addHelpOption();
parser.addOption(QCommandLineOption
(QStringList()<<"x"<<"xml",
"store XML output in <xmlfile>", "xmlfile"));
parser.addOption(QCommandLineOption
(QStringList()<<"r"<<"retries",
"on error retry up to <maxretries> times",
"maxretries", "0"));
parser.addOption(QCommandLineOption
(QStringList()<<"W"<<"width",
"set screenshot size to <width> pixel", "width", "2048"));
parser.addOption(QCommandLineOption
(QStringList()<<"H"<<"height",
"set screenshot size to <height> pixel", "height", "2048"));
parser.addOption(QCommandLineOption
(QStringList()<<"v"<<"version",
"show version information"));
parser.addOption(QCommandLineOption
(QStringList()<<"s"<<"skipped",
"treat skipped test cases as failure in XML output file"));
parser.addOption(QCommandLineOption
(QStringList()<<"t"<<"target-path",
"set screenshot target path to <path>", "path",
QDir().absolutePath()+QDir::separator()+"attachments"));
parser.process(a);
if (parser.isSet("version")) {
std::cout<<*argv<<" "<<VERSION<<std::endl;
return 0;
}
int retries(parser.value("retries").toInt());
int width(parser.value("width").toInt());
int height(parser.value("height").toInt());
QString target(parser.value("target-path"));
p.resize(width, height);
xml::Node testsuites("testsuites");
Q_FOREACH(QString file, parser.positionalArguments()) {
int expectedtestcases(-1);
xml::Node testsuite("testsuite");
testsuite.attr("name") = QFileInfo(file).baseName().toStdString();
testsuite.attr("timestamp") =
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
xml::Node testcase("testcase");
testcase.attr("classname") = "testsuite-preparation";
try {
script.reset();
script.log("=====================");
script.log("TEST: "+file);
script.log("---------------------");
testcase.attr("name") = "open test suite file";
testsuite<<testcase;
QFile f(file);
if (!f.open(QIODevice::ReadOnly))
throw std::runtime_error("cannot open file "+file.toStdString());
QString txt(QString::fromUtf8(f.readAll()));
testcase.attr("name") = "parse test suite file";
testsuite<<testcase;
script.parse(txt.split('\n'));
expectedtestcases = script.steps()+2;
if (failed) {
script.log("FAILED: "+file+" skipped due to previous error");
testcase.attr("name") = "testsuite";
xml::Node failure("failure");
failure.attr("message") = "ignored due to previous failure";
testsuite<<(testcase<<failure);
testsuite.attr("failures") =
mrw::string(expectedtestcases+1-testsuite.children());
if (parser.isSet("skipped")) {
testcase.attr("name") = "skipped test case";
failure.attr("message") = "skipped due to previous failure";
testcase<<failure;
for (int i(testsuite.children()); i<expectedtestcases; ++i)
testsuite<<testcase;
}
} else {
script.run(p.page()->mainFrame(), testsuite,
target+QDir::separator()+QFileInfo(file).baseName(),
true, retries);
testsuite.attr("failures") = "0";
script.log("SUCCESS: "+file);
}
} catch (std::exception& e) {
script.log("FAILED: "+file+" with "+e.what());
xml::Node failure("failure");
failure.attr("message") = Script::xmlattr(e.what()).toStdString();
testsuite[testsuite.children()-1]<<failure;
if (expectedtestcases==-1)
testsuite.attr("failures") = "1";
else
testsuite.attr("failures") =
mrw::string(expectedtestcases+1-testsuite.children());
if (parser.isSet("skipped")) {
testcase.attr("name") = "skipped test case";
failure.attr("message") = "skipped due to previous failure";
testcase<<failure;
for (int i(testsuite.children()); i<expectedtestcases; ++i)
testsuite<<testcase;
failed = true;
}
}
if (expectedtestcases==-1) {
testsuite.attr("tests") = mrw::string(testsuite.children());
} else {
testsuite.attr("tests") = mrw::string(expectedtestcases);
}
if (script.cout().size())
testsuite<<(xml::String("system-out") =
script.xmlattr(script.cout()).toStdString());
if (script.cerr().size())
testsuite<<(xml::String("system-err") =
script.xmlattr(script.cerr()).toStdString());
testsuites<<testsuite;
}
if (parser.isSet("xml")) { // todo: write xml file
std::ofstream xmlfile(parser.value("xml").toStdString());
xmlfile<<testsuites<<std::endl;
}
return failed ? 1 : 0;
} catch (std::exception& e) {
return 1;
}

@ -0,0 +1,9 @@
#include <testgui.hxx>
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
TestGUI w(0, argc>1?argv[1]:"");
w.show();
return a.exec();
}

@ -0,0 +1,30 @@
#-------------------------------------------------
#
# Project created by QtCreator 2014-11-26T10:04:37
#
#-------------------------------------------------
QT += core webkitwidgets
CONFIG += C++11
CODECFORSRC = UTF-8
CODECFORTR = UTF-8
QMAKE_LIBS+=-lxml-cxx
webtester {
QT += gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TEMPLATE = app
TARGET = webtester
SOURCES = webtester.cxx
HEADERS = testgui.hxx commands.hxx webpage.hxx networkaccessmanager.hxx
FORMS = testgui.ui
}
webrunner {
TEMPLATE = app
TARGET = webrunner
SOURCES = webrunner.cxx
HEADERS = commands.hxx webpage.hxx networkaccessmanager.hxx
}
Loading…
Cancel
Save