|
|
|
@ -62,18 +62,6 @@ class Command: public QObject { |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
@ -123,6 +111,19 @@ class Command: public QObject { |
|
|
|
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100); |
|
|
|
|
} |
|
|
|
|
protected: |
|
|
|
|
void subScript(std::shared_ptr<Script> script, |
|
|
|
|
Script* parent, QWebFrame* frame, |
|
|
|
|
QStringList vars = QStringList(), |
|
|
|
|
QStringList args = QStringList()); |
|
|
|
|
QStringList subCommandBlock(QStringList& in) { |
|
|
|
|
QStringList commands; |
|
|
|
|
int pos(-1); |
|
|
|
|
while (in.size() && in[0].size() && in[0][0]==' ') { |
|
|
|
|
if (pos<0) pos=in[0].toStdString().find_first_not_of(' '); |
|
|
|
|
commands += in.takeFirst().mid(pos); |
|
|
|
|
} |
|
|
|
|
return commands; |
|
|
|
|
} |
|
|
|
|
QStringList commaSeparatedList(QString value) { |
|
|
|
|
switch (value.size()>1&&value.at(0)==value.at(value.size()-1) |
|
|
|
|
?value.at(0).toLatin1():'\0') { |
|
|
|
@ -169,8 +170,6 @@ class Command: public QObject { |
|
|
|
|
QString _result; |
|
|
|
|
private: |
|
|
|
|
int _line; |
|
|
|
|
QString _testsuite; |
|
|
|
|
QString _targetdir; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class Empty: public Command { |
|
|
|
@ -463,9 +462,17 @@ class Script: public QObject { |
|
|
|
|
linenr+=oldsize-in.size()) |
|
|
|
|
_script.push_back(parse(in, linenr)); |
|
|
|
|
} |
|
|
|
|
void run(QWebFrame* frame, xml::Node& testsuite, |
|
|
|
|
QString targetdir = QString(), bool screenshots = true, |
|
|
|
|
void run(QWebFrame* frame, QString td = QString(), bool screenshots = true, |
|
|
|
|
int maxretries = 0) { |
|
|
|
|
assert(_internalTestsuiteNode); |
|
|
|
|
run(frame, *_internalTestsuiteNode, td, |
|
|
|
|
screenshots, maxretries); //( @todo extract from parent
|
|
|
|
|
} |
|
|
|
|
void run(QWebFrame* frame, xml::Node& testsuiteNode, |
|
|
|
|
QString td = QString(), bool screenshots = true, |
|
|
|
|
int maxretries = 0) { |
|
|
|
|
_internalTestsuiteNode = &testsuiteNode; |
|
|
|
|
assert(_internalTestsuiteNode); |
|
|
|
|
_timeout = 20; // defaults to 20s
|
|
|
|
|
_ignoreSignalsUntil.clear(); |
|
|
|
|
addSignals(frame); |
|
|
|
@ -476,15 +483,14 @@ class Script: public QObject { |
|
|
|
|
xml::Node testcase("testcase"); |
|
|
|
|
try { |
|
|
|
|
testcase.attr("classname") = |
|
|
|
|
testsuite.attr("name"); |
|
|
|
|
testsuiteNode.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"))); |
|
|
|
|
testsuite(xmlstr(testsuiteNode.attr("name"))); |
|
|
|
|
targetdir(!td.isEmpty() ? td : xmlstr(testsuiteNode.attr("name"))); |
|
|
|
|
try { |
|
|
|
|
if (!(*cmd)->execute(this, frame)) { |
|
|
|
|
_timer.stop(); |
|
|
|
@ -495,17 +501,17 @@ class Script: public QObject { |
|
|
|
|
xmlattr(_cerr).toStdString()); |
|
|
|
|
_cout.clear(); |
|
|
|
|
_cerr.clear(); |
|
|
|
|
testsuite<<testcase; |
|
|
|
|
testsuiteNode<<testcase; |
|
|
|
|
break; // test is successfully finished
|
|
|
|
|
} |
|
|
|
|
} catch (PossibleRetryLoad& e) { |
|
|
|
|
} 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(), |
|
|
|
|
((*cmd)->line(), targetdir(), |
|
|
|
|
QFileInfo(testsuite()).baseName(), |
|
|
|
|
QString("retry-%1") |
|
|
|
|
.arg((ulong)retries, 2, 10, |
|
|
|
|
QLatin1Char('0')), |
|
|
|
@ -557,7 +563,7 @@ class Script: public QObject { |
|
|
|
|
xmlattr(_cerr).toStdString()); |
|
|
|
|
_cout.clear(); |
|
|
|
|
_cerr.clear(); |
|
|
|
|
testsuite<<testcase; |
|
|
|
|
testsuiteNode<<testcase; |
|
|
|
|
} |
|
|
|
|
} catch (Exception& e) { |
|
|
|
|
_timer.stop(); |
|
|
|
@ -568,28 +574,30 @@ class Script: public QObject { |
|
|
|
|
xmlattr(_cerr).toStdString()); |
|
|
|
|
_cout.clear(); |
|
|
|
|
_cerr.clear(); |
|
|
|
|
testsuite<<testcase; |
|
|
|
|
testsuiteNode<<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(), |
|
|
|
|
((*cmd)->line(), targetdir(), |
|
|
|
|
QFileInfo(testsuite()).baseName(), |
|
|
|
|
"error", frame)); |
|
|
|
|
plainlog("[[ATTACHMENT|"+filename+"]]"); |
|
|
|
|
} { |
|
|
|
|
QString filename(Screenshot::screenshot |
|
|
|
|
((*cmd)->line(), (*cmd)->targetdir(), |
|
|
|
|
QFileInfo((*cmd)->testsuite()).baseName(), |
|
|
|
|
((*cmd)->line(), targetdir(), |
|
|
|
|
QFileInfo(testsuite()).baseName(), |
|
|
|
|
"error", frame)); |
|
|
|
|
plainlog("[[ATTACHMENT|"+filename+"]]"); |
|
|
|
|
} |
|
|
|
|
} catch (... ) {} // ignore exception in screenshot
|
|
|
|
|
_internalTestsuiteNode = 0; |
|
|
|
|
throw; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
_internalTestsuiteNode = 0; |
|
|
|
|
removeSignals(frame); |
|
|
|
|
if (!_signals.empty()) throw UnhandledSignals(_signals); |
|
|
|
|
} |
|
|
|
@ -605,6 +613,18 @@ class Script: public QObject { |
|
|
|
|
bool screenshots() { |
|
|
|
|
return _screenshots; |
|
|
|
|
} |
|
|
|
|
void testsuite(QString name) { |
|
|
|
|
_testsuite = name; |
|
|
|
|
} |
|
|
|
|
QString testsuite() { |
|
|
|
|
return _testsuite; |
|
|
|
|
} |
|
|
|
|
void targetdir(QString name) { |
|
|
|
|
_targetdir = name; |
|
|
|
|
} |
|
|
|
|
QString targetdir() { |
|
|
|
|
return _targetdir; |
|
|
|
|
} |
|
|
|
|
Signal getSignal() { |
|
|
|
|
while (!_signals.size()) QCoreApplication::processEvents(); |
|
|
|
|
Signal res(_signals.front()); |
|
|
|
@ -624,11 +644,21 @@ class Script: public QObject { |
|
|
|
|
_variables[name] = value; |
|
|
|
|
_rvariables[value] = name; |
|
|
|
|
} |
|
|
|
|
QString variable(QString name) { |
|
|
|
|
QMap<QString, QString>::iterator it(_variables.find(name)); |
|
|
|
|
if (it==_variables.end()) throw VariableNotFound(name); |
|
|
|
|
return *it; |
|
|
|
|
} |
|
|
|
|
/// Copy context from other script
|
|
|
|
|
void set(const Script& o) { |
|
|
|
|
_variables = o._variables; |
|
|
|
|
_rvariables = o._rvariables; |
|
|
|
|
_timeout = o._timeout; |
|
|
|
|
_clicktype = o._clicktype; |
|
|
|
|
_testsuite = o._testsuite; |
|
|
|
|
_internalTestsuiteNode = o._internalTestsuiteNode; |
|
|
|
|
assert(_internalTestsuiteNode); |
|
|
|
|
_targetdir = o._targetdir; |
|
|
|
|
_cout.clear(); |
|
|
|
|
_cerr.clear(); |
|
|
|
|
_ignoreSignalsUntil.clear(); |
|
|
|
@ -811,6 +841,9 @@ class Script: public QObject { |
|
|
|
|
QMap<QString, std::shared_ptr<Function> > _functions; |
|
|
|
|
int _timeout; |
|
|
|
|
ClickType _clicktype; |
|
|
|
|
QString _testsuite; |
|
|
|
|
QString _targetdir; |
|
|
|
|
xml::Node* _internalTestsuiteNode; ///< only valid within run
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class Do: public Command { |
|
|
|
@ -835,8 +868,7 @@ class Do: public Command { |
|
|
|
|
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(); |
|
|
|
|
cmd->_javascript = subCommandBlock(in).join("\n"); |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
@ -1271,11 +1303,7 @@ class Execute: public Command { |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
cmd->_script = subCommandBlock(in); |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
@ -1786,14 +1814,8 @@ class Function: public Command { |
|
|
|
|
cmd->_name = allargs.takeFirst().trimmed(); |
|
|
|
|
cmd->_vars = commaSeparatedList(allargs.join(' ')); |
|
|
|
|
script->function(cmd->_name, cmd); |
|
|
|
|
QStringList commands; |
|
|
|
|
int pos(-1); |
|
|
|
|
while (in.size() && in[0].size() && in[0][0]==' ') { |
|
|
|
|
if (pos<0) pos=in[0].toStdString().find_first_not_of(' '); |
|
|
|
|
commands += in.takeFirst().mid(pos); |
|
|
|
|
} |
|
|
|
|
cmd->_script = std::shared_ptr<Script>(new Script); |
|
|
|
|
cmd->_script->parse(commands); |
|
|
|
|
cmd->_script->parse(subCommandBlock(in)); |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
@ -1802,27 +1824,9 @@ class Function: public Command { |
|
|
|
|
} |
|
|
|
|
bool call(QStringList args, Script* script, QWebFrame* frame) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
if (args.size()!=_vars.size()) |
|
|
|
|
throw WrongNumberOfArguments(_name, _vars, args); |
|
|
|
|
xml::Node suite("testcase"); |
|
|
|
|
suite.attr("classname") = _name.toStdString(); |
|
|
|
|
suite.attr("name") = testsuite().toStdString(); |
|
|
|
|
_script->set(*script); |
|
|
|
|
for (QStringList::iterator var(_vars.begin()), arg(args.begin()); |
|
|
|
|
var<_vars.end() && arg<args.end(); ++var, ++arg) |
|
|
|
|
_script->set(*var, script->replacevars(*arg)); |
|
|
|
|
try { |
|
|
|
|
connect(_script.get(), SIGNAL(logging(QString)), |
|
|
|
|
script, SLOT(log(QString))); |
|
|
|
|
script->removeSignals(frame); |
|
|
|
|
_script->run(frame, suite, targetdir()); |
|
|
|
|
script->addSignals(frame); |
|
|
|
|
disconnect(_script.get(), SIGNAL(logging(QString)), |
|
|
|
|
script, SLOT(log(QString))); |
|
|
|
|
subScript(_script, script, frame, _vars, args); |
|
|
|
|
} catch (const std::exception& x) { |
|
|
|
|
script->addSignals(frame); |
|
|
|
|
disconnect(_script.get(), SIGNAL(logging(QString)), |
|
|
|
|
script, SLOT(log(QString))); |
|
|
|
|
throw FunctionCallFailed(_name, _vars, args, x); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
@ -1869,6 +1873,67 @@ class Call: public Command { |
|
|
|
|
QStringList _args; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class If: public Command { |
|
|
|
|
public: |
|
|
|
|
QString tag() const { |
|
|
|
|
return "if"; |
|
|
|
|
} |
|
|
|
|
QString description() const { |
|
|
|
|
return |
|
|
|
|
tag()+" <variable> <cmp> <value>\n" |
|
|
|
|
" <command1>\n" |
|
|
|
|
" <command2>\n" |
|
|
|
|
" <...>\n" |
|
|
|
|
"\n\n" |
|
|
|
|
"Execute commands conditionally. " |
|
|
|
|
"The comparision <cmp> can be = ^ ~ < >, " |
|
|
|
|
"which means equal, different, match, " |
|
|
|
|
"less (as integer), bigger (as integer). " |
|
|
|
|
"Match allows a regular expression."; |
|
|
|
|
} |
|
|
|
|
QString command() const { |
|
|
|
|
return tag(); |
|
|
|
|
} |
|
|
|
|
std::shared_ptr<Command> parse(Script*, QString args, |
|
|
|
|
QStringList& in, int) { |
|
|
|
|
std::shared_ptr<If> cmd(new If()); |
|
|
|
|
int pos(args.indexOf(QRegularExpression("[=^~<>]"))); |
|
|
|
|
if (pos<0) throw BadArgument(tag()+" needs a comparision, not: "+args); |
|
|
|
|
cmd->_variable = args.left(pos).trimmed(); |
|
|
|
|
cmd->_cmp = args[pos].toLatin1(); |
|
|
|
|
cmd->_value = args.mid(pos+1).trimmed(); |
|
|
|
|
cmd->_script = std::shared_ptr<Script>(new Script); |
|
|
|
|
cmd->_script->parse(subCommandBlock(in)); |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
QString value(script->replacevars(_value)); |
|
|
|
|
bool check(false); |
|
|
|
|
switch (_cmp) { |
|
|
|
|
case '=': check = script->variable(_variable)==value; |
|
|
|
|
break; |
|
|
|
|
case '^': check = script->variable(_variable)!=value; |
|
|
|
|
break; |
|
|
|
|
case '~': check = |
|
|
|
|
script->variable(_variable).contains(QRegularExpression(value)); |
|
|
|
|
break; |
|
|
|
|
case '<': check = script->variable(_variable).toInt()<value.toInt(); |
|
|
|
|
break; |
|
|
|
|
case '>': check = script->variable(_variable).toInt()>value.toInt(); |
|
|
|
|
break; |
|
|
|
|
default:; |
|
|
|
|
} |
|
|
|
|
if (check) subScript(_script, script, frame); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
|
QString _variable; |
|
|
|
|
char _cmp; |
|
|
|
|
QString _value; |
|
|
|
|
std::shared_ptr<Script> _script; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/* Template:
|
|
|
|
|
class : public Command { |
|
|
|
|
public: |
|
|
|
@ -1899,9 +1964,9 @@ class : public Command { |
|
|
|
|
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)); |
|
|
|
|
QString filename(screenshot(line(), script->targetdir(), |
|
|
|
|
QFileInfo(script->testsuite()).baseName(), |
|
|
|
|
script->replacevars(_filename), frame)); |
|
|
|
|
log["[[ATTACHMENT|"+filename+"]]"]; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
@ -1925,6 +1990,32 @@ inline Logger::~Logger() { |
|
|
|
|
_script->log("---------------------"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
inline void Command::subScript(std::shared_ptr<Script> script, |
|
|
|
|
Script* parent, QWebFrame* frame, |
|
|
|
|
QStringList vars, |
|
|
|
|
QStringList args) { |
|
|
|
|
script->set(*parent); |
|
|
|
|
if (args.size()!=vars.size()) |
|
|
|
|
throw WrongNumberOfArguments(vars, args); |
|
|
|
|
for (QStringList::iterator var(vars.begin()), arg(args.begin()); |
|
|
|
|
var<vars.end() && arg<args.end(); ++var, ++arg) |
|
|
|
|
script->set(*var, parent->replacevars(*arg)); |
|
|
|
|
try { |
|
|
|
|
connect(script.get(), SIGNAL(logging(QString)), |
|
|
|
|
parent, SLOT(log(QString))); |
|
|
|
|
parent->removeSignals(frame); |
|
|
|
|
script->run(frame, parent->targetdir()); |
|
|
|
|
parent->addSignals(frame); |
|
|
|
|
disconnect(script.get(), SIGNAL(logging(QString)), |
|
|
|
|
parent, SLOT(log(QString))); |
|
|
|
|
} catch (const std::exception& x) { |
|
|
|
|
parent->addSignals(frame); |
|
|
|
|
disconnect(script.get(), SIGNAL(logging(QString)), |
|
|
|
|
parent, SLOT(log(QString))); |
|
|
|
|
throw; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
inline void Script::initPrototypes() { |
|
|
|
|
add(new Do); |
|
|
|
|
add(new Load); |
|
|
|
@ -1950,6 +2041,7 @@ inline void Script::initPrototypes() { |
|
|
|
|
add(new SetValue); |
|
|
|
|
add(new Function); |
|
|
|
|
add(new Call); |
|
|
|
|
add(new If); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#endif |
|
|
|
|