/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef COMMANDS_HXX
#define COMMANDS_HXX
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
")
.replace("\n", "
")
+"
";
} break;
}
return cmds.trimmed();
}
void reset() {
_script.clear();
while (!_signals.empty()) _signals.pop();
_timer.stop();
_ignores.clear();
_cout.clear();
_cerr.clear();
_ignoreSignalsUntil.clear();
}
void cleanup() {
reset();
_variables.clear();
_rvariables.clear();
_timeout = 20;
_clicktype = JAVASCRIPT_CLICK;
}
std::shared_ptr 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(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<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 if ((*cmd)->command().startsWith("expect load")) {
QString url2((*cmd)->command());
url2.remove("expect load");
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<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;
_rvariables[value] = name;
}
void unset(QString name) {
_rvariables.remove(_variables[name]);
_variables.remove(name);
}
void timeout(int t) {
_timeout = t;
}
void clicktype(ClickType c) {
_clicktype = c;
}
ClickType clicktype() {
return _clicktype;
}
QString replacevars(QString txt) {
for(QMap::iterator it(_variables.begin());
it!=_variables.end(); ++it)
txt.replace(it.key(), it.value());
return txt;
}
QString insertvars(QString txt) {
QMapIterator it(_rvariables);
it.toBack();
while (it.hasPrevious()) {
it.previous();
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< unknown(QString line) {
if (!line.size()) return std::shared_ptr(new Empty());
if (line[0]=='#') return std::shared_ptr(new Comment(line));
throw UnknownCommand(line); // error
}
void addSignals(QWebFrame* frame) {
connect(dynamic_cast
(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
(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(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> Prototypes;
typedef std::vector> Commands;
Prototypes _prototypes;
Commands _script;
std::queue _signals;
QTimer _timer;
QSet _ignores;
QString _cout;
QString _cerr;
bool _screenshots;
QString _ignoreSignalsUntil;
QMap _variables; ///< variable mapping
QMap _rvariables; ///< reverse variable mapping
int _timeout;
ClickType _clicktype;
};
class Do: public Command {
public:
QString tag() const {
return "do";
}
QString description() const {
return
tag()+" \n \n "
"\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 tag()+" "+_selector+_javascript;
}
std::shared_ptr parse(Script*, QString args,
QStringList& in, int) {
std::shared_ptr 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, script->replacevars(_selector)));
if (element.isNull())
throw ElementNotFound(script->replacevars(_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
tag()+" "
"\n\n"
"Load an URL, the URL is given as parameter in full syntax.";
}
QString command() const {
return tag()+" "+_url;
}
std::shared_ptr parse(Script*, QString args, QStringList&, int) {
std::shared_ptr 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
tag()+" []"
"\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 \n"
" - loadStarted\n"
" - urlChanged \n"
"There is a short hand:\n"
" - load \n"
" stands for the three signals:\n"
" - loadStarted\n"
" - urlChanged \n"
" - loadFinished true";
}
QString command() const {
return tag()+" "+_signal._signal
+(_signal._args.size()?" "+_signal._args.join(' '):QString());
}
std::shared_ptr parse(Script*, QString args, QStringList&, int) {
std::shared_ptr cmd(new Expect());
cmd->_signal._args = args.split(" ");
cmd->_signal._signal = cmd->_signal._args.takeFirst();
return cmd;
}
bool execute(Script* script, QWebFrame*) {
Logger log(this, script);
QList sigs;
if (_signal._signal=="load") { // special signal load
sigs += Signal("loadStarted");
sigs += Signal("urlChanged", _signal._args);
sigs += Signal("loadFinished", "true");
} else {
sigs += _signal;
}
Q_FOREACH(Signal signal, sigs) {
QStringList args;
Q_FOREACH(QString arg, signal._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._signal || (args.size() && args!=lastargs))
throw WrongSignal(signal._signal, args, lastsignal);
}
return true;
}
private:
struct Signal {
Signal(QString s, QStringList a): _signal(s), _args(a) {}
Signal(QString s, QString a): _signal(s), _args(a) {}
Signal(QString s): _signal(s) {}
Signal() {}
QString _signal;
QStringList _args;
};
Signal _signal;
};
class Open: public Command {
public:
QString tag() const {
return "open";
}
QString description() const {
return
tag()+
"\n\n"
"Open the browser window, so you can follow the test steps visually.";
}
QString command() const {
return tag();
}
std::shared_ptr parse(Script*, QString, QStringList&, int) {
std::shared_ptr 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
tag()+" "
"\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 tag()+" "+_time;
}
std::shared_ptr parse(Script*, QString time, QStringList&, int) {
std::shared_ptr 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
tag()+
"\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 tag();
}
std::shared_ptr parse(Script*, QString, QStringList&, int) {
std::shared_ptr 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
tag()+"