|
|
|
@ -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 |
|
|
|
|