|
|
|
@ -105,7 +105,7 @@ class Logger { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class Command: public QObject { |
|
|
|
|
Q_OBJECT; |
|
|
|
|
Q_OBJECT |
|
|
|
|
public: |
|
|
|
|
Command(): _log(true), _line(-1), _indent(0) {} |
|
|
|
|
virtual ~Command() {} |
|
|
|
@ -119,7 +119,7 @@ class Command: public QObject { |
|
|
|
|
log(QString(" FAILED[")+demangle(typeid(e).name())+"]: "+e.what()); |
|
|
|
|
throw e; |
|
|
|
|
} |
|
|
|
|
virtual int steps(Script* parent) { |
|
|
|
|
virtual int steps(Script*) { |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
void line(int linenr) { |
|
|
|
@ -198,10 +198,11 @@ class Command: public QObject { |
|
|
|
|
QStringList args = QStringList()); |
|
|
|
|
QStringList subCommandBlock(QStringList& in) { |
|
|
|
|
QStringList commands; |
|
|
|
|
int pos(-1); |
|
|
|
|
std::string::size_type pos(std::string::npos); |
|
|
|
|
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(' '); |
|
|
|
|
&& (pos==std::string::npos || |
|
|
|
|
pos<=in[0].toStdString().find_first_not_of(' '))) { |
|
|
|
|
if (pos==std::string::npos) pos=in[0].toStdString().find_first_not_of(' '); |
|
|
|
|
commands += in.takeFirst().mid(pos); |
|
|
|
|
} |
|
|
|
|
return commands; |
|
|
|
@ -252,7 +253,7 @@ class Command: public QObject { |
|
|
|
|
for (int i=0; i<repeat; ++i) { |
|
|
|
|
element = frame->findFirstElement(selector); |
|
|
|
|
if (!element.isNull()) return element; |
|
|
|
|
Q_FOREACH(QWebFrame* childFrame, frame->childFrames()) { |
|
|
|
|
for (QWebFrame* childFrame: frame->childFrames()) { |
|
|
|
|
element = find1(childFrame, selector, 1, 0); |
|
|
|
|
if (!element.isNull()) return element; |
|
|
|
|
} |
|
|
|
@ -403,7 +404,7 @@ class Screenshot: public Command { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class RunDownload: public QObject { |
|
|
|
|
Q_OBJECT; |
|
|
|
|
Q_OBJECT |
|
|
|
|
public: |
|
|
|
|
RunDownload(QNetworkReply* reply, QString filename): |
|
|
|
|
_reply(reply), _file(filename) { |
|
|
|
@ -445,7 +446,7 @@ class RunDownload: public QObject { |
|
|
|
|
-# test scripts, see @ref testscript. */ |
|
|
|
|
|
|
|
|
|
class Script: public QObject { |
|
|
|
|
Q_OBJECT; |
|
|
|
|
Q_OBJECT |
|
|
|
|
Q_SIGNALS: |
|
|
|
|
void logging(QString); |
|
|
|
|
void progress(QString, int, int); |
|
|
|
@ -486,17 +487,17 @@ class Script: public QObject { |
|
|
|
|
} |
|
|
|
|
public: |
|
|
|
|
Script(): |
|
|
|
|
_step(0), _clicktype(JAVASCRIPT_CLICK), _command(0), |
|
|
|
|
_screenshots(true), _defaultTimeout(20) { |
|
|
|
|
_step(0), _clicktype(JAVASCRIPT_CLICK), |
|
|
|
|
_screenshots(true), _command(0), _defaultTimeout(20) { |
|
|
|
|
initPrototypes(); |
|
|
|
|
} |
|
|
|
|
Script(const Script& o): |
|
|
|
|
QObject(), |
|
|
|
|
_step(0), |
|
|
|
|
_prototypes(o._prototypes), |
|
|
|
|
_step(0), |
|
|
|
|
_script(o._script), |
|
|
|
|
_command(0), |
|
|
|
|
_screenshots(true) { |
|
|
|
|
_screenshots(true), |
|
|
|
|
_command(0) { |
|
|
|
|
set(o); |
|
|
|
|
} |
|
|
|
|
QString syntax() const { |
|
|
|
@ -861,7 +862,7 @@ class Script: public QObject { |
|
|
|
|
void ignore(QStringList sigs = QStringList()) { |
|
|
|
|
if (sigs.empty()) |
|
|
|
|
sigs<<"loadFinished"<<"loadStarted"<<"frameChanged"<<"titleChanged"<<"urlChanged"; |
|
|
|
|
Q_FOREACH(const QString& sig, sigs) { |
|
|
|
|
for (const QString& sig: sigs) { |
|
|
|
|
log("start ignoring: '"+sig+"'"); |
|
|
|
|
_ignore<<sig; |
|
|
|
|
} |
|
|
|
@ -869,7 +870,7 @@ class Script: public QObject { |
|
|
|
|
void unignore(QStringList sigs = QStringList()) { |
|
|
|
|
if (sigs.empty()) |
|
|
|
|
sigs<<"loadFinished"<<"loadStarted"<<"frameChanged"<<"titleChanged"<<"urlChanged"; |
|
|
|
|
Q_FOREACH(const QString& sig, sigs) { |
|
|
|
|
for (const QString& sig: sigs) { |
|
|
|
|
if (_ignore.contains(sig)) { |
|
|
|
|
log("stop ignoring: '"+sig+"'"); |
|
|
|
|
_ignore.erase(_ignore.find(sig)); |
|
|
|
@ -1190,7 +1191,7 @@ class CommandContainer: public Command { |
|
|
|
|
return countSteps(parent); |
|
|
|
|
} |
|
|
|
|
protected: |
|
|
|
|
virtual int countSteps(Script* parent) const { |
|
|
|
|
virtual int countSteps(Script*) const { |
|
|
|
|
return _script->countSteps()+1; |
|
|
|
|
} |
|
|
|
|
std::shared_ptr<Script> _script; |
|
|
|
@ -1580,7 +1581,7 @@ class Exists: public Command { |
|
|
|
|
QString text(script->replacevars(_text)); |
|
|
|
|
QStringList notfound; |
|
|
|
|
QWebElement firstelement(find(frame, selector, script->timeout()-1)); |
|
|
|
|
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) { |
|
|
|
|
for (QWebElement element: frame->findAllElements(selector)) { |
|
|
|
|
if (text.isEmpty()) return true; // just find element
|
|
|
|
|
if (element.toOuterXml().indexOf(text)!=-1) return true; |
|
|
|
|
if (element.toPlainText().indexOf(text)!=-1) return true; |
|
|
|
@ -1636,7 +1637,7 @@ class Not: public Command { |
|
|
|
|
QString text(script->replacevars(_text)); |
|
|
|
|
QWebElement firstelement(find(frame, selector, |
|
|
|
|
mrw::max(mrw::min(script->timeout()/3, 10), 2))); |
|
|
|
|
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) { |
|
|
|
|
for (QWebElement element: frame->findAllElements(selector)) { |
|
|
|
|
if (text.isEmpty()) |
|
|
|
|
error(log, AssertionFailed("element must not exists: "+selector)); |
|
|
|
|
if (element.toOuterXml().indexOf(text)!=-1) |
|
|
|
@ -1685,7 +1686,7 @@ class Execute: public Command { |
|
|
|
|
QString command(script->replacevars(_command)); |
|
|
|
|
QStringList args; |
|
|
|
|
QString scripttxt(script->replacevars(_script.join("\n"))); |
|
|
|
|
Q_FOREACH(QString arg, _args) args.push_back(script->replacevars(arg)); |
|
|
|
|
for (QString arg: _args) args.push_back(script->replacevars(arg)); |
|
|
|
|
QProcess exec; |
|
|
|
|
exec.setProcessChannelMode(QProcess::MergedChannels); |
|
|
|
|
exec.start(command, args); |
|
|
|
@ -1699,14 +1700,14 @@ class Execute: public Command { |
|
|
|
|
exec.closeWriteChannel(); |
|
|
|
|
if (!exec.waitForFinished(60000) && exec.state()!=QProcess::NotRunning) |
|
|
|
|
error(log, ScriptNotFinished(command, args, scripttxt)); |
|
|
|
|
QString stdout(exec.readAllStandardOutput()); |
|
|
|
|
QString stderr(exec.readAllStandardError()); |
|
|
|
|
_result = stdout; |
|
|
|
|
QString sout(exec.readAllStandardOutput()); |
|
|
|
|
QString serr(exec.readAllStandardError()); |
|
|
|
|
_result = sout; |
|
|
|
|
log("result: "+(_result.size()?_result:"(void)")); |
|
|
|
|
script->log(stdout); |
|
|
|
|
script->log(sout); |
|
|
|
|
if (exec.exitCode()!=0 || exec.exitStatus()!=QProcess::NormalExit) |
|
|
|
|
error(log, ScriptExecutionFailed(command, args, scripttxt, |
|
|
|
|
exec.exitCode(), stdout, stderr)); |
|
|
|
|
exec.exitCode(), sout, serr)); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
@ -1716,7 +1717,7 @@ class Execute: public Command { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class Download: public Command { |
|
|
|
|
Q_OBJECT; |
|
|
|
|
Q_OBJECT |
|
|
|
|
public: |
|
|
|
|
QString tag() const { |
|
|
|
|
return "download"; |
|
|
|
@ -2158,7 +2159,7 @@ class SetValue: public Command { |
|
|
|
|
if (element.tagName()=="SELECT") { |
|
|
|
|
// value is a comma seperated list of option values
|
|
|
|
|
QStringList values(commaSeparatedList(value)); |
|
|
|
|
Q_FOREACH(QWebElement option, element.findAll("option")) { |
|
|
|
|
for (QWebElement option: element.findAll("option")) { |
|
|
|
|
QString name(option.evaluateJavaScript("this.value").toString()); |
|
|
|
|
option.evaluateJavaScript |
|
|
|
|
("this.selected="+QString(values.contains(name)?"true;":"false;")); |
|
|
|
@ -2224,6 +2225,7 @@ class Function: public CommandContainer { |
|
|
|
|
return runScript(log, parentCommand, _script, script, frame, _vars, args); |
|
|
|
|
} catch (const Exception& x) { |
|
|
|
|
error(log, FunctionCallFailed(_name, _vars, args, x)); |
|
|
|
|
return false; // never reached due to exception above
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
@ -2342,7 +2344,7 @@ class If: public CommandContainer { |
|
|
|
|
if (_cmp=="->") { |
|
|
|
|
QWebElement firstelement(find(frame, selector, |
|
|
|
|
mrw::max(mrw::min(script->timeout()/3, 10), 2))); |
|
|
|
|
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) { |
|
|
|
|
for (QWebElement element: frame->findAllElements(selector)) { |
|
|
|
|
if (value.isEmpty() || // just find element
|
|
|
|
|
element.toOuterXml().indexOf(value)!=-1 || |
|
|
|
|
element.toPlainText().indexOf(value)!=-1) { |
|
|
|
@ -2449,7 +2451,7 @@ class While: public CommandContainer { |
|
|
|
|
check = false; |
|
|
|
|
QWebElement firstelement(find(frame, selector, |
|
|
|
|
mrw::max(mrw::min(script->timeout()/3, 10), 2))); |
|
|
|
|
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) { |
|
|
|
|
for (QWebElement element: frame->findAllElements(selector)) { |
|
|
|
|
if (value.isEmpty() || // just find element
|
|
|
|
|
element.toOuterXml().indexOf(value)!=-1 || |
|
|
|
|
element.toPlainText().indexOf(value)!=-1) { |
|
|
|
@ -2511,7 +2513,7 @@ class TestSuite: public Command { |
|
|
|
|
cmd->_name = args; |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
script->testsuite(script->replacevars(_name)); |
|
|
|
|
return true; |
|
|
|
@ -2540,7 +2542,7 @@ class TestCase: public Command { |
|
|
|
|
cmd->_name = args; |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
script->testclass(script->replacevars(_name)); |
|
|
|
|
return true; |
|
|
|
@ -2648,7 +2650,7 @@ class For: public CommandContainer { |
|
|
|
|
" in the loop.\n\n" |
|
|
|
|
"Without values, if there is a global variable with the same name as the" |
|
|
|
|
" local variable the global variable is parsed as if it were the line after" |
|
|
|
|
" the dash (->).\n\n"; |
|
|
|
|
" the dash (->).\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."; |
|
|
|
|
} |
|
|
|
@ -2667,7 +2669,7 @@ class For: public CommandContainer { |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
Q_FOREACH(QString i, _vals.size()?_vals:commaSeparatedList(script->variable(_variable))) { |
|
|
|
|
for (QString i: _vals.size()?_vals:commaSeparatedList(script->variable(_variable))) { |
|
|
|
|
if (!runScript(log, this, _script, script, frame, QStringList()<<_variable, QStringList()<<i)) |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
@ -2706,7 +2708,7 @@ class Echo: public Command { |
|
|
|
|
cmd->_text = args; |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
log(script->replacevars(_text)); |
|
|
|
|
return true; |
|
|
|
@ -2774,7 +2776,7 @@ class ClearCookies: public Command { |
|
|
|
|
QString url(_url); |
|
|
|
|
if (url.isEmpty()) url = frame->url().toString(); |
|
|
|
|
QNetworkCookieJar* cookies = frame->page()->networkAccessManager()->cookieJar(); |
|
|
|
|
Q_FOREACH(QNetworkCookie cookie, cookies->cookiesForUrl(url)) { |
|
|
|
|
for (QNetworkCookie cookie: cookies->cookiesForUrl(url)) { |
|
|
|
|
log("delete cookie "+cookie.name()); |
|
|
|
|
cookies->deleteCookie(cookie); |
|
|
|
|
} |
|
|
|
@ -2810,7 +2812,7 @@ class Include: public CommandContainer { |
|
|
|
|
} catch (Exception& e) { |
|
|
|
|
throw ParseIncludeFailed(cmd->_filename, e.what()); |
|
|
|
|
} |
|
|
|
|
Q_FOREACH(QString key, cmd->_script->functions()) // copy new functions to parent
|
|
|
|
|
for (QString key: cmd->_script->functions()) // copy new functions to parent
|
|
|
|
|
script->function(key, cmd->_script->function(key)); |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
@ -2820,6 +2822,7 @@ class Include: public CommandContainer { |
|
|
|
|
return runScript(log, this, _script, script, frame); |
|
|
|
|
} catch (Exception& e) { |
|
|
|
|
error(log, ExecuteIncludeFailed(_filename, e.what())); |
|
|
|
|
return false; // never reached due to exception above
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
@ -2837,7 +2840,7 @@ class Case: public Command { |
|
|
|
|
" <cmp1> <value1>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <cmp1> <value2>\n" |
|
|
|
|
" <cmp2> <value2>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <command>\n" |
|
|
|
|
" <...>\n" |
|
|
|
@ -2873,7 +2876,7 @@ class Case: public Command { |
|
|
|
|
} |
|
|
|
|
QString command() const { |
|
|
|
|
QString body; |
|
|
|
|
Q_FOREACH(Condition condition, _conditions) { |
|
|
|
|
for (Condition condition: _conditions) { |
|
|
|
|
body += "\n "+condition.cmp+" "+condition.value+"\n " |
|
|
|
|
+condition.script->print().join("\n "); |
|
|
|
|
} |
|
|
|
@ -2886,6 +2889,7 @@ class Case: public Command { |
|
|
|
|
if (!args.size()) throw BadArgument(tag()+" requires a <variable> or <selector>"); |
|
|
|
|
cmd->_variable = args; |
|
|
|
|
QStringList body(subCommandBlock(in)); |
|
|
|
|
if (!body.size()) throw BadArgument(tag()+" requires a body"); |
|
|
|
|
while (body.size()) { |
|
|
|
|
++line; |
|
|
|
|
QStringList parts(body.takeFirst().split(' ')); |
|
|
|
@ -2902,7 +2906,7 @@ class Case: public Command { |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
Logger log(this, script, false); |
|
|
|
|
QString selector(script->replacevars(_variable)); |
|
|
|
|
Q_FOREACH(Condition condition, _conditions) { |
|
|
|
|
for (Condition condition: _conditions) { |
|
|
|
|
QString value(script->replacevars(condition.value)); |
|
|
|
|
bool check(false); |
|
|
|
|
if (condition.cmp=="default") { |
|
|
|
@ -2911,7 +2915,7 @@ class Case: public Command { |
|
|
|
|
} else if (condition.cmp=="->") { |
|
|
|
|
QWebElement firstelement(find(frame, selector, |
|
|
|
|
mrw::max(mrw::min(script->timeout()/3, 10), 2))); |
|
|
|
|
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) { |
|
|
|
|
for (QWebElement element: frame->findAllElements(selector)) { |
|
|
|
|
if (value.isEmpty() || // just find element
|
|
|
|
|
element.toOuterXml().indexOf(value)!=-1 || |
|
|
|
|
element.toPlainText().indexOf(value)!=-1) { |
|
|
|
@ -2948,7 +2952,7 @@ class Case: public Command { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
private: |
|
|
|
|
int countSteps(Script* parent) { |
|
|
|
|
int countSteps(Script*) { |
|
|
|
|
int res1(0); |
|
|
|
|
for (auto condition: _conditions) { |
|
|
|
|
int res2(condition.script->countSteps()+1); |
|
|
|
@ -3031,7 +3035,7 @@ class Auth: public Command { |
|
|
|
|
} |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
script->auth(_realm, _username, _password); |
|
|
|
|
return true; |
|
|
|
@ -3065,7 +3069,7 @@ class Ignore: public Command { |
|
|
|
|
cmd->_signals = args.split(' ', QString::SkipEmptyParts); |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
script->ignore(_signals); |
|
|
|
|
return true; |
|
|
|
@ -3097,7 +3101,7 @@ class UnIgnore: public Command { |
|
|
|
|
cmd->_signals = args.split(' ', QString::SkipEmptyParts); |
|
|
|
|
return cmd; |
|
|
|
|
} |
|
|
|
|
bool execute(Script* script, QWebFrame* frame) { |
|
|
|
|
bool execute(Script* script, QWebFrame*) { |
|
|
|
|
Logger log(this, script); |
|
|
|
|
script->unignore(_signals); |
|
|
|
|
return true; |
|
|
|
@ -3151,13 +3155,14 @@ inline Logger::Logger(Command* command, Script* script, bool showLines): |
|
|
|
|
if (command) { |
|
|
|
|
_previous = _script->command(); |
|
|
|
|
_script->command(command); |
|
|
|
|
if (_command->log()) |
|
|
|
|
if (_command->log()) { |
|
|
|
|
if (showLines) |
|
|
|
|
_script->log("\\ "+_command->command(), _command); |
|
|
|
|
else |
|
|
|
|
_script->log("\\ "+_command->command().split('\n').first(), _command); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
inline void Logger::operator()(QString txt) { |
|
|
|
|
if (!_command || _command->log()) _script->log(" "+txt, _command); |
|
|
|
|
} |
|
|
|
@ -3182,7 +3187,7 @@ inline std::shared_ptr<Script> Command::subParser(Script* parent, const QStringL |
|
|
|
|
const QString& file, |
|
|
|
|
int line, int indent) { |
|
|
|
|
std::shared_ptr<Script> res(new Script); |
|
|
|
|
Q_FOREACH(QString key, parent->functions()) // copy functions from parent
|
|
|
|
|
for (QString key: parent->functions()) // copy functions from parent
|
|
|
|
|
res->function(key, parent->function(key)); |
|
|
|
|
res->parse(in, file, line+1, indent+1); |
|
|
|
|
return res; |
|
|
|
@ -3216,7 +3221,7 @@ inline bool Command::runScript(Logger& log, Command* parentCommand, |
|
|
|
|
disconnect(&scriptCopy, SIGNAL(logging(QString)), |
|
|
|
|
parent, SLOT(parentlog(QString))); |
|
|
|
|
parentCommand->_result = scriptCopy.result(); |
|
|
|
|
Q_FOREACH(QString key, scriptCopy.variables()) // copy new variables to parent
|
|
|
|
|
for (QString key: scriptCopy.variables()) // copy new variables to parent
|
|
|
|
|
if (!vars.contains(key)) parent->set(key, scriptCopy.variable(key)); |
|
|
|
|
parent->ignore(scriptCopy); // copy ignore list
|
|
|
|
|
if (parentCommand->_result.size()) |
|
|
|
|