new feature: if tatement for conditions in functions

master
Marc Wäckerlin 10 years ago
parent bf6b4764db
commit ee1ebd8ca9
  1. 6
      ChangeLog
  2. 12
      ax_init_standard_project.m4
  3. 2
      bootstrap.sh
  4. 4
      docker/Dockerfile
  5. 224
      src/commands.hxx
  6. 13
      src/exceptions.hxx
  7. 6
      src/testgui.hxx

@ -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 2015-05-06 16:56 marc
* debian/control.in: reprepo requires a section * debian/control.in: reprepo requires a section

@ -65,7 +65,7 @@ AC_DEFUN([AX_ADD_MAKEFILE_TARGET_DEP], [
# $1 = variable name # $1 = variable name
AC_DEFUN([AX_SUBST], [ AC_DEFUN([AX_SUBST], [
AC_SUBST([$1]) 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//\'/\'\"\'\"\'} 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) AX_SUBST(DISTRO)
BUILD_NUMBER=${BUILD_NUMBER:-1} BUILD_NUMBER=${BUILD_NUMBER:-1}
AX_SUBST(BUILD_NUMBER) AX_SUBST(BUILD_NUMBER)
BUILD_DATE=$(date -R) BUILD_DATE=$(date +"%Y-%m-%d %H:%M %Z")
AX_SUBST(BUILD_DATE) AX_SUBST(BUILD_DATE)
if test -f "${PROJECT_NAME}-logo.png"; then if test -f "${PROJECT_NAME}-logo.png"; then
PROJECT_LOGO="${PROJECT_NAME}-logo.png" PROJECT_LOGO="${PROJECT_NAME}-logo.png"
@ -365,8 +365,9 @@ AC_DEFUN([AX_PKG_REQUIRE], [
if test ${$1_found} -eq 0; then if test ${$1_found} -eq 0; then
for p in /usr/include ${$1_CFLAGS}; do for p in /usr/include ${$1_CFLAGS}; do
$1_file=$(find ${p#-I} -name $3) $1_file=$(find ${p#-I} -name $3)
if test -e ${$1_file}; then 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 $1_found=1
break break
fi fi
@ -396,7 +397,8 @@ AC_DEFUN([AX_PKG_REQUIRE], [
for p in /usr/include ${$1_CFLAGS}; do for p in /usr/include ${$1_CFLAGS}; do
$1_file=$(find ${p#-I} -name $3) $1_file=$(find ${p#-I} -name $3)
if test -e ${$1_file}; then 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 $1_found=1
break break
fi fi

@ -216,7 +216,7 @@ done
HEADER='## @id '"\$Id\$"' 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 # Feel free to change it or even remove and rebuild it, up to your needs
# #
## 1 2 3 4 5 6 7 8 ## 1 2 3 4 5 6 7 8

@ -1,5 +1,5 @@
# docker build --pull --force-rm --rm -t dev0004:5000/libraries/webtester . # docker build --pull --force-rm --rm -t dev0004:5000/library/webtester .
# docker push dev0004:5000/libraries/webtester # docker push dev0004:5000/libray/webtester
FROM ubuntu:latest FROM ubuntu:latest
MAINTAINER "Marc Wäckerlin" MAINTAINER "Marc Wäckerlin"

@ -62,18 +62,6 @@ class Command: public QObject {
int line() const { int line() const {
return _line; return _line;
} }
void testsuite(QString name) {
_testsuite = name;
}
QString testsuite() {
return _testsuite;
}
void targetdir(QString name) {
_targetdir = name;
}
QString targetdir() {
return _targetdir;
}
bool log() { bool log() {
return _log; return _log;
} }
@ -123,6 +111,19 @@ class Command: public QObject {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100); QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
} }
protected: 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) { QStringList commaSeparatedList(QString value) {
switch (value.size()>1&&value.at(0)==value.at(value.size()-1) switch (value.size()>1&&value.at(0)==value.at(value.size()-1)
?value.at(0).toLatin1():'\0') { ?value.at(0).toLatin1():'\0') {
@ -169,8 +170,6 @@ class Command: public QObject {
QString _result; QString _result;
private: private:
int _line; int _line;
QString _testsuite;
QString _targetdir;
}; };
class Empty: public Command { class Empty: public Command {
@ -463,9 +462,17 @@ class Script: public QObject {
linenr+=oldsize-in.size()) linenr+=oldsize-in.size())
_script.push_back(parse(in, linenr)); _script.push_back(parse(in, linenr));
} }
void run(QWebFrame* frame, xml::Node& testsuite, void run(QWebFrame* frame, QString td = QString(), bool screenshots = true,
QString targetdir = 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) { int maxretries = 0) {
_internalTestsuiteNode = &testsuiteNode;
assert(_internalTestsuiteNode);
_timeout = 20; // defaults to 20s _timeout = 20; // defaults to 20s
_ignoreSignalsUntil.clear(); _ignoreSignalsUntil.clear();
addSignals(frame); addSignals(frame);
@ -476,15 +483,14 @@ class Script: public QObject {
xml::Node testcase("testcase"); xml::Node testcase("testcase");
try { try {
testcase.attr("classname") = testcase.attr("classname") =
testsuite.attr("name"); testsuiteNode.attr("name");
//xmlattr((*cmd)->command(), true).toStdString(); //xmlattr((*cmd)->command(), true).toStdString();
testcase.attr("name") = testcase.attr("name") =
xmlattr((*cmd)->tag(), true).toStdString(); xmlattr((*cmd)->tag(), true).toStdString();
if (!_ignores.size() || (*cmd)->tag()=="label") { // not ignored if (!_ignores.size() || (*cmd)->tag()=="label") { // not ignored
_timer.start(_timeout*1000); _timer.start(_timeout*1000);
(*cmd)->testsuite(xmlstr(testsuite.attr("name"))); testsuite(xmlstr(testsuiteNode.attr("name")));
(*cmd)->targetdir(!targetdir.isEmpty() ? targetdir : targetdir(!td.isEmpty() ? td : xmlstr(testsuiteNode.attr("name")));
xmlstr(testsuite.attr("name")));
try { try {
if (!(*cmd)->execute(this, frame)) { if (!(*cmd)->execute(this, frame)) {
_timer.stop(); _timer.stop();
@ -495,17 +501,17 @@ class Script: public QObject {
xmlattr(_cerr).toStdString()); xmlattr(_cerr).toStdString());
_cout.clear(); _cout.clear();
_cerr.clear(); _cerr.clear();
testsuite<<testcase; testsuiteNode<<testcase;
break; // test is successfully finished break; // test is successfully finished
} }
} catch (PossibleRetryLoad& e) { } catch (PossibleRetryLoad& e) {
_timer.stop(); _timer.stop();
// timeout may happen during load due to bad internet connection // timeout may happen during load due to bad internet connection
if (screenshots) if (screenshots)
try { // take a screenshot on error try { // take a screenshot on error
QString filename(Screenshot::screenshot QString filename(Screenshot::screenshot
((*cmd)->line(), (*cmd)->targetdir(), ((*cmd)->line(), targetdir(),
QFileInfo((*cmd)->testsuite()).baseName(), QFileInfo(testsuite()).baseName(),
QString("retry-%1") QString("retry-%1")
.arg((ulong)retries, 2, 10, .arg((ulong)retries, 2, 10,
QLatin1Char('0')), QLatin1Char('0')),
@ -557,7 +563,7 @@ class Script: public QObject {
xmlattr(_cerr).toStdString()); xmlattr(_cerr).toStdString());
_cout.clear(); _cout.clear();
_cerr.clear(); _cerr.clear();
testsuite<<testcase; testsuiteNode<<testcase;
} }
} catch (Exception& e) { } catch (Exception& e) {
_timer.stop(); _timer.stop();
@ -568,28 +574,30 @@ class Script: public QObject {
xmlattr(_cerr).toStdString()); xmlattr(_cerr).toStdString());
_cout.clear(); _cout.clear();
_cerr.clear(); _cerr.clear();
testsuite<<testcase; testsuiteNode<<testcase;
removeSignals(frame); removeSignals(frame);
e.line((*cmd)->line()); e.line((*cmd)->line());
if (screenshots) if (screenshots)
try { // write html source and take a last screenshot on error try { // write html source and take a last screenshot on error
{ {
QString filename(Screenshot::sourceHtml QString filename(Screenshot::sourceHtml
((*cmd)->line(), (*cmd)->targetdir(), ((*cmd)->line(), targetdir(),
QFileInfo((*cmd)->testsuite()).baseName(), QFileInfo(testsuite()).baseName(),
"error", frame)); "error", frame));
plainlog("[[ATTACHMENT|"+filename+"]]"); plainlog("[[ATTACHMENT|"+filename+"]]");
} { } {
QString filename(Screenshot::screenshot QString filename(Screenshot::screenshot
((*cmd)->line(), (*cmd)->targetdir(), ((*cmd)->line(), targetdir(),
QFileInfo((*cmd)->testsuite()).baseName(), QFileInfo(testsuite()).baseName(),
"error", frame)); "error", frame));
plainlog("[[ATTACHMENT|"+filename+"]]"); plainlog("[[ATTACHMENT|"+filename+"]]");
} }
} catch (... ) {} // ignore exception in screenshot } catch (... ) {} // ignore exception in screenshot
_internalTestsuiteNode = 0;
throw; throw;
} }
} }
_internalTestsuiteNode = 0;
removeSignals(frame); removeSignals(frame);
if (!_signals.empty()) throw UnhandledSignals(_signals); if (!_signals.empty()) throw UnhandledSignals(_signals);
} }
@ -605,6 +613,18 @@ class Script: public QObject {
bool screenshots() { bool screenshots() {
return _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() { Signal getSignal() {
while (!_signals.size()) QCoreApplication::processEvents(); while (!_signals.size()) QCoreApplication::processEvents();
Signal res(_signals.front()); Signal res(_signals.front());
@ -624,11 +644,21 @@ class Script: public QObject {
_variables[name] = value; _variables[name] = value;
_rvariables[value] = name; _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) { void set(const Script& o) {
_variables = o._variables; _variables = o._variables;
_rvariables = o._rvariables; _rvariables = o._rvariables;
_timeout = o._timeout; _timeout = o._timeout;
_clicktype = o._clicktype; _clicktype = o._clicktype;
_testsuite = o._testsuite;
_internalTestsuiteNode = o._internalTestsuiteNode;
assert(_internalTestsuiteNode);
_targetdir = o._targetdir;
_cout.clear(); _cout.clear();
_cerr.clear(); _cerr.clear();
_ignoreSignalsUntil.clear(); _ignoreSignalsUntil.clear();
@ -811,6 +841,9 @@ class Script: public QObject {
QMap<QString, std::shared_ptr<Function> > _functions; QMap<QString, std::shared_ptr<Function> > _functions;
int _timeout; int _timeout;
ClickType _clicktype; ClickType _clicktype;
QString _testsuite;
QString _targetdir;
xml::Node* _internalTestsuiteNode; ///< only valid within run
}; };
class Do: public Command { class Do: public Command {
@ -835,8 +868,7 @@ class Do: public Command {
QStringList& in, int) { QStringList& in, int) {
std::shared_ptr<Do> cmd(new Do()); std::shared_ptr<Do> cmd(new Do());
cmd->_selector = args; cmd->_selector = args;
while (in.size() && in[0].size() && in[0][0]==' ') cmd->_javascript = subCommandBlock(in).join("\n");
cmd->_javascript += "\n"+in.takeFirst();
return cmd; return cmd;
} }
bool execute(Script* script, QWebFrame* frame) { bool execute(Script* script, QWebFrame* frame) {
@ -1271,11 +1303,7 @@ class Execute: public Command {
std::shared_ptr<Execute> cmd(new Execute()); std::shared_ptr<Execute> cmd(new Execute());
cmd->_args = args.split(' '); cmd->_args = args.split(' ');
cmd->_command = cmd->_args.takeFirst(); cmd->_command = cmd->_args.takeFirst();
int pos(-1); cmd->_script = subCommandBlock(in);
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; return cmd;
} }
bool execute(Script* script, QWebFrame*) { bool execute(Script* script, QWebFrame*) {
@ -1786,14 +1814,8 @@ class Function: public Command {
cmd->_name = allargs.takeFirst().trimmed(); cmd->_name = allargs.takeFirst().trimmed();
cmd->_vars = commaSeparatedList(allargs.join(' ')); cmd->_vars = commaSeparatedList(allargs.join(' '));
script->function(cmd->_name, cmd); 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 = std::shared_ptr<Script>(new Script);
cmd->_script->parse(commands); cmd->_script->parse(subCommandBlock(in));
return cmd; return cmd;
} }
bool execute(Script* script, QWebFrame*) { bool execute(Script* script, QWebFrame*) {
@ -1802,27 +1824,9 @@ class Function: public Command {
} }
bool call(QStringList args, Script* script, QWebFrame* frame) { bool call(QStringList args, Script* script, QWebFrame* frame) {
Logger log(this, script); 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 { try {
connect(_script.get(), SIGNAL(logging(QString)), subScript(_script, script, frame, _vars, args);
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)));
} catch (const std::exception& x) { } catch (const std::exception& x) {
script->addSignals(frame);
disconnect(_script.get(), SIGNAL(logging(QString)),
script, SLOT(log(QString)));
throw FunctionCallFailed(_name, _vars, args, x); throw FunctionCallFailed(_name, _vars, args, x);
} }
return true; return true;
@ -1869,6 +1873,67 @@ class Call: public Command {
QStringList _args; 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: /* Template:
class : public Command { class : public Command {
public: public:
@ -1899,9 +1964,9 @@ class : public Command {
inline bool Screenshot::execute(Script* script, QWebFrame* frame) { inline bool Screenshot::execute(Script* script, QWebFrame* frame) {
if (!script->screenshots()) return true; if (!script->screenshots()) return true;
Logger log(this, script); Logger log(this, script);
QString filename(screenshot(line(), targetdir(), QString filename(screenshot(line(), script->targetdir(),
QFileInfo(testsuite()).baseName(), QFileInfo(script->testsuite()).baseName(),
_filename, frame)); script->replacevars(_filename), frame));
log["[[ATTACHMENT|"+filename+"]]"]; log["[[ATTACHMENT|"+filename+"]]"];
return true; return true;
} }
@ -1925,6 +1990,32 @@ inline Logger::~Logger() {
_script->log("---------------------"); _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() { inline void Script::initPrototypes() {
add(new Do); add(new Do);
add(new Load); add(new Load);
@ -1950,6 +2041,7 @@ inline void Script::initPrototypes() {
add(new SetValue); add(new SetValue);
add(new Function); add(new Function);
add(new Call); add(new Call);
add(new If);
} }
#endif #endif

@ -221,9 +221,9 @@ class ScriptExecutionFailed: public ScriptFailed {
class WrongNumberOfArguments: public TestFailed { class WrongNumberOfArguments: public TestFailed {
public: public:
WrongNumberOfArguments(QString name, QStringList vars, QStringList args): WrongNumberOfArguments(QStringList vars, QStringList args):
TestFailed(QString("%1 has %2 arguments, but %3 were given") TestFailed(QString("function has %1 arguments, but %2 were given")
.arg(name).arg(vars.size()).arg(args.size())) { .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 #endif

@ -368,7 +368,11 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
} else { } else {
appendCommand("click "+map(selected)); 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; _typing = true;
} }
} else { } else {

Loading…
Cancel
Save