new feature: if tatement for conditions in functions
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
2015-05-06 23:09 marc
|
||||
|
||||
* ChangeLog, bootstrap.sh, doc/doxyfile.in,
|
||||
scripts/doxygen-webtester.sed, scripts/example.wt,
|
||||
scripts/makefile.am: better doxygen support
|
||||
|
||||
2015-05-06 16:56 marc
|
||||
|
||||
* debian/control.in: reprepo requires a section
|
||||
|
@@ -65,7 +65,7 @@ AC_DEFUN([AX_ADD_MAKEFILE_TARGET_DEP], [
|
||||
# $1 = variable name
|
||||
AC_DEFUN([AX_SUBST], [
|
||||
AC_SUBST([$1])
|
||||
tmp_var=$(echo "${$1}" | sed ':a;N;$!ba;s/\n/\\n/g')
|
||||
tmp_var=$(echo "${$1}" | awk 1 ORS='\\n')
|
||||
tmp_var=${tmp_var//\"/\\\"}
|
||||
tmp_var=${tmp_var//\'/\'\"\'\"\'}
|
||||
tmp_var=${tmp_var//#/\\#}
|
||||
@@ -105,7 +105,7 @@ AC_DEFUN([AX_INIT_STANDARD_PROJECT], [
|
||||
AX_SUBST(DISTRO)
|
||||
BUILD_NUMBER=${BUILD_NUMBER:-1}
|
||||
AX_SUBST(BUILD_NUMBER)
|
||||
BUILD_DATE=$(date -R)
|
||||
BUILD_DATE=$(date +"%Y-%m-%d %H:%M %Z")
|
||||
AX_SUBST(BUILD_DATE)
|
||||
if test -f "${PROJECT_NAME}-logo.png"; then
|
||||
PROJECT_LOGO="${PROJECT_NAME}-logo.png"
|
||||
@@ -365,8 +365,9 @@ AC_DEFUN([AX_PKG_REQUIRE], [
|
||||
if test ${$1_found} -eq 0; then
|
||||
for p in /usr/include ${$1_CFLAGS}; do
|
||||
$1_file=$(find ${p#-I} -name $3)
|
||||
if test -e ${$1_file}; then
|
||||
$1_CFLAGS="${$1_CFLAGS} -I${$1_file%/*}"
|
||||
if test -e "${$1_file}"; then
|
||||
AC_MSG_NOTICE([Header file $3 found in sub path as ${$1_file}])
|
||||
$1_CFLAGS="${$1_CFLAGS} -I${$1_file%/$3}"
|
||||
$1_found=1
|
||||
break
|
||||
fi
|
||||
@@ -396,7 +397,8 @@ AC_DEFUN([AX_PKG_REQUIRE], [
|
||||
for p in /usr/include ${$1_CFLAGS}; do
|
||||
$1_file=$(find ${p#-I} -name $3)
|
||||
if test -e ${$1_file}; then
|
||||
$1_CFLAGS="${$1_CFLAGS} -I${$1_file%/*}"
|
||||
AC_MSG_NOTICE([Header file $3 found in sub path as ${$1_file}])
|
||||
$1_CFLAGS="${$1_CFLAGS} -I${$1_file%/$3}"
|
||||
$1_found=1
|
||||
break
|
||||
fi
|
||||
|
@@ -216,7 +216,7 @@ done
|
||||
|
||||
HEADER='## @id '"\$Id\$"'
|
||||
#
|
||||
# This file has been added by '${MY_NAME}' on '$(date -R)'
|
||||
# This file has been added by '${MY_NAME}' on '$(date +"%Y-%m-%d %H:%M %Z")'
|
||||
# Feel free to change it or even remove and rebuild it, up to your needs
|
||||
#
|
||||
## 1 2 3 4 5 6 7 8
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# docker build --pull --force-rm --rm -t dev0004:5000/libraries/webtester .
|
||||
# docker push dev0004:5000/libraries/webtester
|
||||
# docker build --pull --force-rm --rm -t dev0004:5000/library/webtester .
|
||||
# docker push dev0004:5000/libray/webtester
|
||||
FROM ubuntu:latest
|
||||
MAINTAINER "Marc Wäckerlin"
|
||||
|
||||
|
224
src/commands.hxx
224
src/commands.hxx
@@ -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
|
||||
|
@@ -221,9 +221,9 @@ class ScriptExecutionFailed: public ScriptFailed {
|
||||
|
||||
class WrongNumberOfArguments: public TestFailed {
|
||||
public:
|
||||
WrongNumberOfArguments(QString name, QStringList vars, QStringList args):
|
||||
TestFailed(QString("%1 has %2 arguments, but %3 were given")
|
||||
.arg(name).arg(vars.size()).arg(args.size())) {
|
||||
WrongNumberOfArguments(QStringList vars, QStringList args):
|
||||
TestFailed(QString("function has %1 arguments, but %2 were given")
|
||||
.arg(vars.size()).arg(args.size())) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -246,4 +246,11 @@ class FunctionNotFound: public TestFailed {
|
||||
}
|
||||
};
|
||||
|
||||
class VariableNotFound: public TestFailed {
|
||||
public:
|
||||
VariableNotFound(QString name):
|
||||
TestFailed("variable not found: "+name) {
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -368,7 +368,11 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
||||
} else {
|
||||
appendCommand("click "+map(selected));
|
||||
}
|
||||
if (_lastFocused.tagName()=="INPUT") {
|
||||
if (_lastFocused.tagName()=="TEXTAREA" ||
|
||||
_lastFocused.tagName()=="INPUT" &&
|
||||
_lastFocused.attribute("type")=="text") {
|
||||
// user clickt in a text edit field, so not the klick
|
||||
// is important, but the text that will be typed
|
||||
_typing = true;
|
||||
}
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user