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 Command;
|
||||
class Function;
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
@@ -121,7 +122,20 @@ class Command: public QObject {
|
||||
while (QTime::currentTime()<dieTime)
|
||||
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,
|
||||
int repeat = 2, int sleepsec = 1) {
|
||||
QWebElement element;
|
||||
@@ -418,6 +432,7 @@ class Script: public QObject {
|
||||
reset();
|
||||
_variables.clear();
|
||||
_rvariables.clear();
|
||||
_functions.clear();
|
||||
_timeout = 20;
|
||||
_clicktype = JAVASCRIPT_CLICK;
|
||||
}
|
||||
@@ -609,10 +624,29 @@ class Script: public QObject {
|
||||
_variables[name] = value;
|
||||
_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) {
|
||||
_rvariables.remove(_variables[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) {
|
||||
_timeout = t;
|
||||
}
|
||||
@@ -637,24 +671,6 @@ class Script: public QObject {
|
||||
}
|
||||
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) {
|
||||
connect(dynamic_cast<NetworkAccessManager*>
|
||||
(frame->page()->networkAccessManager()),
|
||||
@@ -703,6 +719,24 @@ class Script: public QObject {
|
||||
this, SLOT(urlChanged(const QUrl&)));
|
||||
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 add(Command* c) {
|
||||
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
|
||||
@@ -774,6 +808,7 @@ class Script: public QObject {
|
||||
QString _ignoreSignalsUntil;
|
||||
QMap<QString, QString> _variables; ///< variable mapping
|
||||
QMap<LenString, LenString> _rvariables; ///< reverse variable mapping
|
||||
QMap<QString, std::shared_ptr<Function> > _functions;
|
||||
int _timeout;
|
||||
ClickType _clicktype;
|
||||
};
|
||||
@@ -1217,7 +1252,7 @@ class Execute: public Command {
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
tag()+" <command>\n <line1>\n <line2>\n <...>"
|
||||
tag()+" <command>\n <line1>\n <line2>\n <...>"
|
||||
"\n\n"
|
||||
"Execute <command>. The command can have space separated arguments. "
|
||||
"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"
|
||||
" of a command. A command should be a command that produces an"
|
||||
" 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 {
|
||||
if (_next)
|
||||
@@ -1663,7 +1699,7 @@ class SetValue: public Command {
|
||||
QString description() const {
|
||||
return
|
||||
tag()+" <selector> -> '<value>'\n"+
|
||||
tag()+" <selector> -> '<value1>', '<value2>', ..."
|
||||
tag()+" <selector> -> '<value1>', '<value2>', <...>"
|
||||
"\n\n"
|
||||
"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"
|
||||
@@ -1697,18 +1733,7 @@ class SetValue: public Command {
|
||||
QString value(script->replacevars(_value));
|
||||
if (element.tagName()=="SELECT") {
|
||||
// value is a comma seperated list of option values
|
||||
QStringList values;
|
||||
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(", *"));
|
||||
}
|
||||
}
|
||||
QStringList values(commaSeparatedList(value));
|
||||
Q_FOREACH(QWebElement option, element.findAll("option")) {
|
||||
QString name(option.evaluateJavaScript("this.value").toString());
|
||||
option.evaluateJavaScript
|
||||
@@ -1730,6 +1755,120 @@ class SetValue: public Command {
|
||||
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:
|
||||
class : public Command {
|
||||
public:
|
||||
@@ -1746,7 +1885,7 @@ class : public Command {
|
||||
return tag();
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args,
|
||||
QStringList& in, int) {
|
||||
QStringList&, int) {
|
||||
std::shared_ptr<> cmd(new ());
|
||||
return cmd;
|
||||
}
|
||||
@@ -1809,6 +1948,8 @@ inline void Script::initPrototypes() {
|
||||
add(new ClientCertificate);
|
||||
add(new ::ClickType);
|
||||
add(new SetValue);
|
||||
add(new Function);
|
||||
add(new Call);
|
||||
}
|
||||
|
||||
#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
|
||||
|
@@ -269,7 +269,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
||||
int pos1(text.lastIndexOf(QRegularExpression("^do ")));
|
||||
int pos2(text.lastIndexOf(QRegularExpression("^load ")));
|
||||
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->moveCursor(QTextCursor::End);
|
||||
_testscript->ensureCursorVisible();
|
||||
|
Reference in New Issue
Block a user