new feature: define and call functions using the two new commands function and call
This commit is contained in:
211
src/commands.hxx
211
src/commands.hxx
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
class Script;
|
class Script;
|
||||||
class Command;
|
class Command;
|
||||||
|
class Function;
|
||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
public:
|
public:
|
||||||
@@ -121,7 +122,20 @@ class Command: public QObject {
|
|||||||
while (QTime::currentTime()<dieTime)
|
while (QTime::currentTime()<dieTime)
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||||
}
|
}
|
||||||
public:
|
protected:
|
||||||
|
QStringList commaSeparatedList(QString value) {
|
||||||
|
switch (value.size()>1&&value.at(0)==value.at(value.size()-1)
|
||||||
|
?value.at(0).toLatin1():'\0') {
|
||||||
|
case '"': case '\'': {
|
||||||
|
return value.mid(1, value.size()-2)
|
||||||
|
.split(QRegularExpression(QString(value[0])+", *"
|
||||||
|
+QString(value[0])));
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
return value.split(QRegularExpression(", *"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
static QWebElement find(QWebFrame* frame, QString selector,
|
static QWebElement find(QWebFrame* frame, QString selector,
|
||||||
int repeat = 2, int sleepsec = 1) {
|
int repeat = 2, int sleepsec = 1) {
|
||||||
QWebElement element;
|
QWebElement element;
|
||||||
@@ -418,6 +432,7 @@ class Script: public QObject {
|
|||||||
reset();
|
reset();
|
||||||
_variables.clear();
|
_variables.clear();
|
||||||
_rvariables.clear();
|
_rvariables.clear();
|
||||||
|
_functions.clear();
|
||||||
_timeout = 20;
|
_timeout = 20;
|
||||||
_clicktype = JAVASCRIPT_CLICK;
|
_clicktype = JAVASCRIPT_CLICK;
|
||||||
}
|
}
|
||||||
@@ -609,10 +624,29 @@ class Script: public QObject {
|
|||||||
_variables[name] = value;
|
_variables[name] = value;
|
||||||
_rvariables[value] = name;
|
_rvariables[value] = name;
|
||||||
}
|
}
|
||||||
|
void set(const Script& o) {
|
||||||
|
_variables = o._variables;
|
||||||
|
_rvariables = o._rvariables;
|
||||||
|
_timeout = o._timeout;
|
||||||
|
_clicktype = o._clicktype;
|
||||||
|
_cout.clear();
|
||||||
|
_cerr.clear();
|
||||||
|
_ignoreSignalsUntil.clear();
|
||||||
|
_functions.clear();
|
||||||
|
}
|
||||||
void unset(QString name) {
|
void unset(QString name) {
|
||||||
_rvariables.remove(_variables[name]);
|
_rvariables.remove(_variables[name]);
|
||||||
_variables.remove(name);
|
_variables.remove(name);
|
||||||
}
|
}
|
||||||
|
void function(QString name, std::shared_ptr<Function> f) {
|
||||||
|
_functions[name] = f;
|
||||||
|
}
|
||||||
|
std::shared_ptr<Function> function(QString name) {
|
||||||
|
QMap<QString, std::shared_ptr<Function> >::iterator
|
||||||
|
it(_functions.find(name));
|
||||||
|
if (it==_functions.end()) throw FunctionNotFound(name);
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
void timeout(int t) {
|
void timeout(int t) {
|
||||||
_timeout = t;
|
_timeout = t;
|
||||||
}
|
}
|
||||||
@@ -637,24 +671,6 @@ class Script: public QObject {
|
|||||||
}
|
}
|
||||||
return txt;
|
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) {
|
void addSignals(QWebFrame* frame) {
|
||||||
connect(dynamic_cast<NetworkAccessManager*>
|
connect(dynamic_cast<NetworkAccessManager*>
|
||||||
(frame->page()->networkAccessManager()),
|
(frame->page()->networkAccessManager()),
|
||||||
@@ -703,6 +719,24 @@ class Script: public QObject {
|
|||||||
this, SLOT(urlChanged(const QUrl&)));
|
this, SLOT(urlChanged(const QUrl&)));
|
||||||
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(timeout()));
|
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(timeout()));
|
||||||
}
|
}
|
||||||
|
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 initPrototypes();
|
void initPrototypes();
|
||||||
void add(Command* c) {
|
void add(Command* c) {
|
||||||
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
|
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
|
||||||
@@ -774,6 +808,7 @@ class Script: public QObject {
|
|||||||
QString _ignoreSignalsUntil;
|
QString _ignoreSignalsUntil;
|
||||||
QMap<QString, QString> _variables; ///< variable mapping
|
QMap<QString, QString> _variables; ///< variable mapping
|
||||||
QMap<LenString, LenString> _rvariables; ///< reverse variable mapping
|
QMap<LenString, LenString> _rvariables; ///< reverse variable mapping
|
||||||
|
QMap<QString, std::shared_ptr<Function> > _functions;
|
||||||
int _timeout;
|
int _timeout;
|
||||||
ClickType _clicktype;
|
ClickType _clicktype;
|
||||||
};
|
};
|
||||||
@@ -1217,7 +1252,7 @@ class Execute: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
tag()+" <command>\n <line1>\n <line2>\n <...>"
|
tag()+" <command>\n <line1>\n <line2>\n <...>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Execute <command>. The command can have space separated arguments. "
|
"Execute <command>. The command can have space separated arguments. "
|
||||||
"Following lines that are intended by at least "
|
"Following lines that are intended by at least "
|
||||||
@@ -1416,7 +1451,8 @@ class Set: public Command {
|
|||||||
"Sets the value of a variable either to a constant, or to the output"
|
"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"
|
" of a command. A command should be a command that produces an"
|
||||||
" output, such as «do», which returns the result of JavaScript or"
|
" output, such as «do», which returns the result of JavaScript or"
|
||||||
" «execute», which returns the output of the executed command.";
|
" «execute», which returns the output of the executed command."
|
||||||
|
" All variables are global with regrad to functions.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
if (_next)
|
if (_next)
|
||||||
@@ -1663,7 +1699,7 @@ class SetValue: public Command {
|
|||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
tag()+" <selector> -> '<value>'\n"+
|
tag()+" <selector> -> '<value>'\n"+
|
||||||
tag()+" <selector> -> '<value1>', '<value2>', ..."
|
tag()+" <selector> -> '<value1>', '<value2>', <...>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Set the selected element to a given value. It is mostly the same as"
|
"Set the selected element to a given value. It is mostly the same as"
|
||||||
" the following constuct, except that options in a select are evaluated"
|
" the following constuct, except that options in a select are evaluated"
|
||||||
@@ -1697,18 +1733,7 @@ class SetValue: public Command {
|
|||||||
QString value(script->replacevars(_value));
|
QString value(script->replacevars(_value));
|
||||||
if (element.tagName()=="SELECT") {
|
if (element.tagName()=="SELECT") {
|
||||||
// value is a comma seperated list of option values
|
// value is a comma seperated list of option values
|
||||||
QStringList values;
|
QStringList values(commaSeparatedList(value));
|
||||||
switch (value.size()>1&&value.at(0)==value.at(value.size()-1)
|
|
||||||
?value.at(0).toLatin1():'\0') {
|
|
||||||
case '"': case '\'': {
|
|
||||||
values = value.mid(1, value.size()-2)
|
|
||||||
.split(QRegularExpression(QString(value[0])+", *"
|
|
||||||
+QString(value[0])));
|
|
||||||
} break;
|
|
||||||
default: {
|
|
||||||
values = value.split(QRegularExpression(", *"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Q_FOREACH(QWebElement option, element.findAll("option")) {
|
Q_FOREACH(QWebElement option, element.findAll("option")) {
|
||||||
QString name(option.evaluateJavaScript("this.value").toString());
|
QString name(option.evaluateJavaScript("this.value").toString());
|
||||||
option.evaluateJavaScript
|
option.evaluateJavaScript
|
||||||
@@ -1730,6 +1755,120 @@ class SetValue: public Command {
|
|||||||
QString _value;
|
QString _value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Function: public Command {
|
||||||
|
public:
|
||||||
|
QString tag() const {
|
||||||
|
return "function";
|
||||||
|
}
|
||||||
|
QString description() const {
|
||||||
|
return
|
||||||
|
tag()+" <name> [<var1>, <var2>, <...>]\n"
|
||||||
|
" <command1>\n"
|
||||||
|
" <command2>\n"
|
||||||
|
" <...>\n"
|
||||||
|
"\n\n"
|
||||||
|
"Define a function with arguments. The arguments are treated like"
|
||||||
|
" local variables. In a sequence of scripts within the same testrun,"
|
||||||
|
" functions are inherited from all followin scripts, so you can first"
|
||||||
|
" load a script file that contains all functions. Within the same file,"
|
||||||
|
" a function can be called before the definition.\n\n"
|
||||||
|
"If you quote the values, then quote all values with the same"
|
||||||
|
" quotes. If you need a comma within a value, you must quote.";
|
||||||
|
}
|
||||||
|
QString command() const {
|
||||||
|
return tag()+" "+_name+" "+_vars.join(" ");
|
||||||
|
}
|
||||||
|
std::shared_ptr<Command> parse(Script* script, QString args,
|
||||||
|
QStringList& in, int) {
|
||||||
|
std::shared_ptr<Function> cmd(new Function());
|
||||||
|
if (!args.size()) throw BadArgument(tag()+" requires a <name>");
|
||||||
|
QStringList allargs(args.split(" "));
|
||||||
|
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);
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
bool execute(Script* script, QWebFrame*) {
|
||||||
|
Logger log(this, script);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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)));
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
QString _name;
|
||||||
|
QStringList _vars;
|
||||||
|
std::shared_ptr<Script> _script;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Call: public Command {
|
||||||
|
public:
|
||||||
|
QString tag() const {
|
||||||
|
return "call";
|
||||||
|
}
|
||||||
|
QString description() const {
|
||||||
|
return
|
||||||
|
tag()+" <name> ['<arg1>', '<arg2>', ...]"
|
||||||
|
"\n\n"
|
||||||
|
"Calls a function. The number of arguments must be exactly the same"
|
||||||
|
" as in the function definition.\n\n"
|
||||||
|
"If you quote the values, then quote all values with the same"
|
||||||
|
" quotes. If you need a comma within a value, you must quote.";
|
||||||
|
}
|
||||||
|
QString command() const {
|
||||||
|
return tag()+" "+_name+(_args.size()?" '"+_args.join("', '")+"'":"");
|
||||||
|
}
|
||||||
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
|
QStringList&, int) {
|
||||||
|
std::shared_ptr<Call> cmd(new Call());
|
||||||
|
if (!args.size()) throw BadArgument(tag()+" requires a <name>");
|
||||||
|
QStringList allargs(args.split(" "));
|
||||||
|
cmd->_name = allargs.takeFirst().trimmed();
|
||||||
|
cmd->_args = commaSeparatedList(allargs.join(' '));
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
bool execute(Script* script, QWebFrame* frame) {
|
||||||
|
Logger log(this, script);
|
||||||
|
script->function(_name)->call(_args, script, frame);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
QString _name;
|
||||||
|
QStringList _args;
|
||||||
|
};
|
||||||
|
|
||||||
/* Template:
|
/* Template:
|
||||||
class : public Command {
|
class : public Command {
|
||||||
public:
|
public:
|
||||||
@@ -1746,7 +1885,7 @@ class : public Command {
|
|||||||
return tag();
|
return tag();
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args,
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
QStringList& in, int) {
|
QStringList&, int) {
|
||||||
std::shared_ptr<> cmd(new ());
|
std::shared_ptr<> cmd(new ());
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
@@ -1809,6 +1948,8 @@ inline void Script::initPrototypes() {
|
|||||||
add(new ClientCertificate);
|
add(new ClientCertificate);
|
||||||
add(new ::ClickType);
|
add(new ::ClickType);
|
||||||
add(new SetValue);
|
add(new SetValue);
|
||||||
|
add(new Function);
|
||||||
|
add(new Call);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -219,4 +219,31 @@ 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())) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FunctionCallFailed: public TestFailed {
|
||||||
|
public:
|
||||||
|
FunctionCallFailed(QString name, QStringList vars, QStringList args,
|
||||||
|
const std::exception& x):
|
||||||
|
TestFailed("function call failed: "+name) {
|
||||||
|
for (QStringList::iterator var(vars.begin()), arg(args.begin());
|
||||||
|
var<vars.end() && arg<args.end(); ++var, ++arg)
|
||||||
|
_what += " "+*var+"="+*arg;
|
||||||
|
_what += QString("; reason: ")+x.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FunctionNotFound: public TestFailed {
|
||||||
|
public:
|
||||||
|
FunctionNotFound(QString name):
|
||||||
|
TestFailed("function not found: "+name) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -269,7 +269,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
int pos1(text.lastIndexOf(QRegularExpression("^do ")));
|
int pos1(text.lastIndexOf(QRegularExpression("^do ")));
|
||||||
int pos2(text.lastIndexOf(QRegularExpression("^load ")));
|
int pos2(text.lastIndexOf(QRegularExpression("^load ")));
|
||||||
int pos3(text.lastIndexOf(QRegularExpression("^click ")));
|
int pos3(text.lastIndexOf(QRegularExpression("^click ")));
|
||||||
text.insert(mrw::max(pos1, pos2, pos3), "download "+filename);
|
text.insert(std::max({pos1, pos2, pos3}), "download "+filename);
|
||||||
_testscript->setPlainText(text);
|
_testscript->setPlainText(text);
|
||||||
_testscript->moveCursor(QTextCursor::End);
|
_testscript->moveCursor(QTextCursor::End);
|
||||||
_testscript->ensureCursorVisible();
|
_testscript->ensureCursorVisible();
|
||||||
|
Reference in New Issue
Block a user