new commands: include, case and fail; new emacs wt-mode for webtester files
This commit is contained in:
247
src/commands.hxx
247
src/commands.hxx
@@ -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
|
||||
|
@@ -269,4 +269,25 @@ class CheckFailed: public TestFailed {
|
||||
}
|
||||
};
|
||||
|
||||
class OpenIncludeFailed: public TestFailed {
|
||||
public:
|
||||
OpenIncludeFailed(QString file):
|
||||
TestFailed(QString("open include file %1 failed").arg(file)) {
|
||||
}
|
||||
};
|
||||
|
||||
class ParseIncludeFailed: public TestFailed {
|
||||
public:
|
||||
ParseIncludeFailed(QString file, QString msg):
|
||||
TestFailed(QString("parse include file %1 failed with: %2").arg(file).arg(msg)) {
|
||||
}
|
||||
};
|
||||
|
||||
class ExecuteIncludeFailed: public TestFailed {
|
||||
public:
|
||||
ExecuteIncludeFailed(QString file, QString msg):
|
||||
TestFailed(QString("error in included file %1: %2").arg(file).arg(msg)) {
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user