|
|
|
@ -176,7 +176,8 @@ class Command: public QObject { |
|
|
|
|
QStringList subCommandBlock(QStringList& in) { |
|
|
|
|
QStringList commands; |
|
|
|
|
int pos(-1); |
|
|
|
|
while (in.size() && in[0].size() && in[0][0]==' ') { |
|
|
|
|
while (in.size() && in[0].size() && in[0][0]==' ' |
|
|
|
|
&& pos<=(signed)in[0].toStdString().find_first_not_of(' ')) { |
|
|
|
|
if (pos<0) pos=in[0].toStdString().find_first_not_of(' '); |
|
|
|
|
commands += in.takeFirst().mid(pos); |
|
|
|
|
} |
|
|
|
@ -805,6 +806,9 @@ class Script: public QObject { |
|
|
|
|
_rvariables.remove(_variables[name]); |
|
|
|
|
_variables.remove(name); |
|
|
|
|
} |
|
|
|
|
QStringList functions() { |
|
|
|
|
return _functions.keys(); |
|
|
|
|
} |
|
|
|
|
void function(QString name, std::shared_ptr<Function> f) { |
|
|
|
|
_functions[name] = f; |
|
|
|
|
} |
|
|
|
@ -826,7 +830,7 @@ class Script: public QObject { |
|
|
|
|
QString replacevars(QString txt) { |
|
|
|
|
for(QMap<QString, QString>::iterator it(_variables.begin()); |
|
|
|
|
it!=_variables.end(); ++it) |
|
|
|
|
txt.replace(it.key(), it.value()); |
|
|
|
|
txt.replace(it.key(), it.value(), Qt::CaseSensitive); |
|
|
|
|
return txt; |
|
|
|
|
} |
|
|
|
|
QString insertvars(QString txt) { |
|
|
|
@ -834,7 +838,7 @@ class Script: public QObject { |
|
|
|
|
it.toBack(); |
|
|
|
|
while (it.hasPrevious()) { |
|
|
|
|
it.previous(); |
|
|
|
|
txt.replace(it.key(), it.value()); |
|
|
|
|
txt.replace(it.key(), it.value(), Qt::CaseSensitive); |
|
|
|
|
} |
|
|
|
|
return txt; |
|
|
|
|
} |
|
|
|
@ -1471,7 +1475,7 @@ class Execute: public Command { |
|
|
|
|
script.replaceInStrings(QRegularExpression("^"), " "); |
|
|
|
|
return tag()+" "+_command |
|
|
|
|
+(_args.size()?" "+_args.join(' '):QString()) |
|
|
|
|
+(script.size()?"\n"+script.join("\n"):QString()); |
|
|
|
|
+(script.size()?"\n "+script.join("\n "):QString()); |
|
|
|
|
} |
|
|
|
|
std::shared_ptr<Command> parse(Script*, QString args, |
|
|
|
|
QStringList& in, QString, int, int) { |
|
|
|
@ -2096,8 +2100,8 @@ class If: public Command { |
|
|
|
|
"\n\n" |
|
|
|
|
"Execute commands conditionally. " |
|
|
|
|
"The first variant compares a variable to a value. " |
|
|
|
|
"The comparision <cmp> can be = ^ . ~ < >, " |
|
|
|
|
"which means equal, different, contains, match, " |
|
|
|
|
"The comparision <cmp> can be = ! . ^ ~ < >, " |
|
|
|
|
"which means equal, different, contains, contains not, match, " |
|
|
|
|
"less (as integer), bigger (as integer). " |
|
|
|
|
"Match allows a regular expression. " |
|
|
|
|
"The second variant checks for a text in a selector, " |
|
|
|
@ -2106,13 +2110,13 @@ class If: public Command { |
|
|
|
|
} |
|
|
|
|
QString command() const { |
|
|
|
|
return tag()+" "+_variable+" "+_cmp+" "+_value |
|
|
|
|
+(_script.get()?"\n"+_script->print().join("\n "):""); |
|
|
|
|
+(_script.get()?"\n "+_script->print().join("\n "):""); |
|
|
|
|
} |
|
|
|
|
std::shared_ptr<Command> parse(Script*, QString args, |
|
|
|
|
QStringList& in, QString file, int line, |
|
|
|
|
int indent) { |
|
|
|
|
std::shared_ptr<If> cmd(new If()); |
|
|
|
|
int pos(args.indexOf(QRegularExpression("[=^.~<>]"))); |
|
|
|
|
int pos(args.indexOf(QRegularExpression("[=!.^~<>]"))); |
|
|
|
|
int len(1); |
|
|
|
|
if (args.contains("->")) { |
|
|
|
|
pos = args.indexOf("->"); |
|
|
|
@ -2126,7 +2130,7 @@ class If: public Command { |
|
|
|
|
cmd->_value = args.mid(pos+len).trimmed(); |
|
|
|
|
cmd->_script = std::shared_ptr<Script>(new Script); |
|
|
|
|
cmd->_script->parse(subCommandBlock(in), file, line+1, indent+1); |
|
|
|
|
if (in.size() && in.first().contains(QRegularExpression("^else *$"))) { |
|
|
|
|
if (in.size() && in.first().contains(QRegularExpression("^ *else *$"))) { |
|
|
|
|
in.removeFirst(); |
|
|
|
|
cmd->_else = std::shared_ptr<Script>(new Script); |
|
|
|
|
cmd->_else->parse(subCommandBlock(in), file, line+1, indent+1); |
|
|
|
@ -2153,10 +2157,12 @@ class If: public Command { |
|
|
|
|
switch (_cmp[0].toLatin1()) { |
|
|
|
|
case '=': check = script->variable(_variable)==value; |
|
|
|
|
break; |
|
|
|
|
case '^': check = script->variable(_variable)!=value; |
|
|
|
|
case '!': check = script->variable(_variable)!=value; |
|
|
|
|
break; |
|
|
|
|
case '.': check = script->variable(_variable).contains(value); |
|
|
|
|
break; |
|
|
|
|
case '^': check = !script->variable(_variable).contains(value); |
|
|
|
|
break; |
|
|
|
|
case '~': check = |
|
|
|
|
script->variable(_variable).contains(QRegularExpression(value)); |
|
|
|
|
break; |
|
|
|
@ -2166,7 +2172,7 @@ class If: public Command { |
|
|
|
|
break; |
|
|
|
|
default:; |
|
|
|
|
} |
|
|
|
|
log(QString("evaluated expression to ")+(check?"true":"false")+":" |
|
|
|
|
log(QString("evaluated expression to ")+(check?"true":"false")+": " |
|
|
|
|
+script->variable(_variable)+" "+_cmp+" "+value); |
|
|
|
|
} |
|
|
|
|
if (check) return runScript(this, _script, script, frame); |
|
|
|
@ -2255,8 +2261,8 @@ class Check: public Command { |
|
|
|
|
" output, such as <do>, which returns the result of JavaScript or" |
|
|
|
|
" <execute>, which returns the output of the executed command, or" |
|
|
|
|
" <call>, which returns the result of the last command. " |
|
|
|
|
"The comparision <cmp> can be = ^ . ~ < >, " |
|
|
|
|
"which means equal, different, contains match, " |
|
|
|
|
"The comparision <cmp> can be = ! . ^ ~ < >, " |
|
|
|
|
"which means equal, different, contains, contains not, match, " |
|
|
|
|
"less (as integer), bigger (as integer). " |
|
|
|
|
"Match allows a regular expression. " |
|
|
|
|
" less than < (integers), larger than > (integers)"; |
|
|
|
@ -2272,7 +2278,7 @@ class Check: public Command { |
|
|
|
|
int indent) { |
|
|
|
|
std::shared_ptr<Check> cmd(new Check()); |
|
|
|
|
cmd->_next = 0; |
|
|
|
|
int pos(args.indexOf(QRegularExpression("[=^.~<>]"))); |
|
|
|
|
int pos(args.indexOf(QRegularExpression("[=!.^~<>]"))); |
|
|
|
|
if (pos<0) throw BadArgument(tag()+" needs a comparision, not: "+args); |
|
|
|
|
cmd->_value1 = args.left(pos).trimmed(); |
|
|
|
|
cmd->_cmp = args[pos].toLatin1(); |
|
|
|
@ -2294,8 +2300,9 @@ class Check: public Command { |
|
|
|
|
bool check(false); |
|
|
|
|
switch (_cmp) { |
|
|
|
|
case '=': check = value1==value2; break; |
|
|
|
|
case '^': check = value1!=value2; break; |
|
|
|
|
case '!': check = value1!=value2; break; |
|
|
|
|
case '.': check = value1.contains(value2); break; |
|
|
|
|
case '^': check = !value1.contains(value2); break; |
|
|
|
|
case '~': check = value1.contains(QRegularExpression(value2)); break; |
|
|
|
|
case '<': check = value1.toInt()<value2.toInt(); break; |
|
|
|
|
case '>': check = value1.toInt()>value2.toInt(); break; |
|
|
|
@ -2459,6 +2466,209 @@ class ClearCookies: public Command { |
|
|
|
|
QString _url; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class Include: public Command { |
|
|
|
|
public: |
|
|
|
|
QString tag() const { |
|
|
|
|
return "include"; |
|
|
|
|
} |
|
|
|
|
QString description() const { |
|
|
|
|
return |
|
|
|
|
tag()+" <filename>" |
|
|
|
|
"\n\n" |
|
|
|
|
"Include a test script."; |
|
|
|
|
} |
|
|
|
|
QString command() const { |
|
|
|
|
return tag()+" "+_filename; |
|
|
|
|
} |
|
|
|
|
std::shared_ptr<Command> parse(Script* script, QString args, |
|
|
|
|
QStringList&, QString, int, int indent) { |
|
|
|
|
std::shared_ptr<Include> cmd(new Include()); |
|
|
|
|
cmd->_filename = args; |
|
|
|
|
QFile f(cmd->_filename); |
|
|
|
|
if (!f.open(QIODevice::ReadOnly)) throw OpenIncludeFailed(cmd->_filename); |
|
|
|
|
QString txt(QString::fromUtf8(f.readAll())); |
|
|
|
|
try { |
|
|
|
|
cmd->_script = std::shared_ptr<Script>(new Script); |
|
|
|
|
cmd->_script->parse(txt.split('\n'), cmd->_filename, 1, indent+1); |
|
|
|
|
} catch (std::exception& e) { |
|
|
|
|
throw ParseIncludeFailed(cmd->_filename, e.what()); |
|
|
|
|
} |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) try { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
return runScript(this, _script, script, frame); |
|
|
|
|
} catch (std::exception& e) { |
|
|
|
|
throw ExecuteIncludeFailed(_filename, e.what()); |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
|
QString _filename; |
|
|
|
|
std::shared_ptr<Script> _script; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class Case: public Command { |
|
|
|
|
public: |
|
|
|
|
QString tag() const { |
|
|
|
|
return "case"; |
|
|
|
|
} |
|
|
|
|
QString description() const { |
|
|
|
|
return |
|
|
|
|
tag()+" <variable>\n" |
|
|
|
|
" <cmp1> <value1>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <cmp1> <value2>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <...>\n" |
|
|
|
|
" default\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <...>\n" |
|
|
|
|
"\n\n"+ |
|
|
|
|
tag()+" <selector>\n" |
|
|
|
|
" -> <text1>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" -> <text2>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <...>\n" |
|
|
|
|
" default\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <...>\n" |
|
|
|
|
"\n\n" |
|
|
|
|
"Execute commands conditionally depending on a variable. " |
|
|
|
|
"It is equivalent to neested if-else-if commands." |
|
|
|
|
"The first variant compares a variable to a value. " |
|
|
|
|
"The comparision <cmp> can be = ! . ^ ~ < >, " |
|
|
|
|
"which means equal, different, contains, contains not, match, " |
|
|
|
|
"less (as integer), bigger (as integer). " |
|
|
|
|
"Match allows a regular expression. " |
|
|
|
|
"The second variant checks for a text in a selector, " |
|
|
|
|
"similar to command exists. " |
|
|
|
|
"There is an optional default part that applies if none " |
|
|
|
|
"of the previous conditions match."; |
|
|
|
|
} |
|
|
|
|
QString command() const { |
|
|
|
|
QString body; |
|
|
|
|
Q_FOREACH(Condition condition, _conditions) { |
|
|
|
|
body += "\n "+condition.cmp+" "+condition.value+"\n " |
|
|
|
|
+condition.script->print().join("\n "); |
|
|
|
|
} |
|
|
|
|
return tag()+" "+_variable+body; |
|
|
|
|
} |
|
|
|
|
std::shared_ptr<Command> parse(Script*, QString args, |
|
|
|
|
QStringList& in, QString file, int line, |
|
|
|
|
int indent) { |
|
|
|
|
std::shared_ptr<Case> cmd(new Case()); |
|
|
|
|
if (!args.size()) throw BadArgument(tag()+" requires a <variable> or <selector>"); |
|
|
|
|
cmd->_variable = args; |
|
|
|
|
QStringList body(subCommandBlock(in)); |
|
|
|
|
while (body.size()) { |
|
|
|
|
++line; |
|
|
|
|
QStringList parts(body.takeFirst().split(' ')); |
|
|
|
|
QString cmp(parts.takeFirst()); |
|
|
|
|
QString value(parts.join(' ')); |
|
|
|
|
if (!cmp.contains(QRegularExpression("^[=!.^~<>]|->|default$"))) |
|
|
|
|
throw BadArgument(tag()+" needs a comparision, not: "+cmp); |
|
|
|
|
std::shared_ptr<Script> script(std::shared_ptr<Script>(new Script)); |
|
|
|
|
script->parse(subCommandBlock(body), file, line+1, indent+2); |
|
|
|
|
cmd->_conditions.append(Condition(cmp, value, script)); |
|
|
|
|
} |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
Logger log(this, script, false); |
|
|
|
|
QString selector(script->replacevars(_variable)); |
|
|
|
|
Q_FOREACH(Condition condition, _conditions) { |
|
|
|
|
QString value(script->replacevars(condition.value)); |
|
|
|
|
bool check(false); |
|
|
|
|
if (condition.cmp=="default") { |
|
|
|
|
log("terminate with default branch"); |
|
|
|
|
check = true; |
|
|
|
|
} else if (condition.cmp=="->") { |
|
|
|
|
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) { |
|
|
|
|
if (value.isEmpty() || // just find element
|
|
|
|
|
element.toOuterXml().indexOf(value)!=-1 || |
|
|
|
|
element.toPlainText().indexOf(value)!=-1) { |
|
|
|
|
check = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
log(QString("evaluated expression to ")+(check?"true":"false")+": " |
|
|
|
|
+selector+" "+condition.cmp+" "+value); |
|
|
|
|
} else { |
|
|
|
|
switch (condition.cmp[0].toLatin1()) { |
|
|
|
|
case '=': check = script->variable(_variable)==value; |
|
|
|
|
break; |
|
|
|
|
case '!': check = script->variable(_variable)!=value; |
|
|
|
|
break; |
|
|
|
|
case '.': check = script->variable(_variable).contains(value); |
|
|
|
|
break; |
|
|
|
|
case '^': check = !script->variable(_variable).contains(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:; |
|
|
|
|
} |
|
|
|
|
log(QString("evaluated expression to ")+(check?"true":"false")+": " |
|
|
|
|
+script->variable(_variable)+" "+condition.cmp+" "+value); |
|
|
|
|
} |
|
|
|
|
if (check) return runScript(this, condition.script, script, frame); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
|
struct Condition { |
|
|
|
|
Condition(QString c, QString v, std::shared_ptr<Script> s): |
|
|
|
|
cmp(c), value(v), script(s) { |
|
|
|
|
} |
|
|
|
|
Condition() {} |
|
|
|
|
QString cmp; |
|
|
|
|
QString value; |
|
|
|
|
std::shared_ptr<Script> script; |
|
|
|
|
}; |
|
|
|
|
QString _variable; |
|
|
|
|
QVector<Condition> _conditions; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class Fail: public Command { |
|
|
|
|
public: |
|
|
|
|
QString tag() const { |
|
|
|
|
return "fail"; |
|
|
|
|
} |
|
|
|
|
QString description() const { |
|
|
|
|
return |
|
|
|
|
tag()+" <text>" |
|
|
|
|
"\n\n" |
|
|
|
|
"Fail with error text."; |
|
|
|
|
} |
|
|
|
|
QString command() const { |
|
|
|
|
return tag()+" "+_text; |
|
|
|
|
} |
|
|
|
|
std::shared_ptr<Command> parse(Script*, QString args, |
|
|
|
|
QStringList&, QString, int, int) { |
|
|
|
|
std::shared_ptr<Fail> cmd(new Fail()); |
|
|
|
|
cmd->_text = args; |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
throw TestFailed(script->replacevars(_text)); |
|
|
|
|
return true; // dummy
|
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
|
QString _text; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Template:
|
|
|
|
|
class : public Command { |
|
|
|
|
public: |
|
|
|
@ -2544,8 +2754,10 @@ inline bool Command::runScript(Command* parentCommand, |
|
|
|
|
disconnect(&scriptCopy, SIGNAL(logging(QString)), |
|
|
|
|
parent, SLOT(parentlog(QString))); |
|
|
|
|
parentCommand->_result = scriptCopy.result(); |
|
|
|
|
Q_FOREACH(QString key, scriptCopy.variables()) |
|
|
|
|
Q_FOREACH(QString key, scriptCopy.variables()) // copy new variables to parent
|
|
|
|
|
if (!vars.contains(key)) parent->set(key, scriptCopy.variable(key)); |
|
|
|
|
Q_FOREACH(QString key, scriptCopy.functions()) // copy new functions to parent
|
|
|
|
|
parent->function(key, scriptCopy.function(key)); |
|
|
|
|
if (parentCommand->_result.size()) |
|
|
|
|
parent->log("result: "+parentCommand->_result); |
|
|
|
|
return res; |
|
|
|
@ -2590,6 +2802,9 @@ inline void Script::initPrototypes() { |
|
|
|
|
add(new Echo); |
|
|
|
|
add(new OfflineStoragePath); |
|
|
|
|
add(new ClearCookies); |
|
|
|
|
add(new Include); |
|
|
|
|
add(new Case); |
|
|
|
|
add(new Fail); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#endif |
|
|
|
|