new option for timeout, longer timeout in shell, new untested command auth to set basic authentication credentials
This commit is contained in:
33
README
33
README
@@ -1,18 +1,25 @@
|
|||||||
Test Your Web Application: GUI Web Testing Environment + Script Runner
|
# Framework for Automated Web Application Testing
|
||||||
|
|
||||||
Webtester consists of two binaries: webtester to interactively create your web application tests and webrunner to run your test scripts.
|
|
||||||
|
|
||||||
There is a test GUI including browser to record user input while he surfs on the web and a test runner to run (recorded) test scripts. The tests can be integrated e.g. in a jenkins build job. It has been tested on Wordpress, Dokuwiki and Joomla pages. Joomla is difficult due to Javascript-Moo-Tools pollution. There's some specific support, that may help a bit, but to test Joomla sites, you need a lot of experience. Concluson: Avoid Joomla.
|
There is a test GUI including browser to record user input while he surfs on the web and a test runner to run (recorded) test scripts. The tests can be integrated e.g. in a jenkins build job. It has been tested on Wordpress, Dokuwiki and Joomla pages. Joomla is difficult due to Javascript-Moo-Tools pollution. There's some specific support, that may help a bit, but to test Joomla sites, you need a lot of experience. Concluson: Avoid Joomla.
|
||||||
|
|
||||||
Sample Script to search my old homepage on Google, klick on the link, there click on tab «Computer» and check the title for the text «Marcs Computerblog»:
|
Sample Script to search my old homepage on Google, klick on the link, there click on tab «Computer» and check the title for the text «Marcs Computerblog»:
|
||||||
|
|
||||||
load https://google.com
|
load https://google.com
|
||||||
expect load
|
expect loadStarted
|
||||||
setvalue input[name="q"] -> 'Marc Wäckerlin';
|
expect urlChanged
|
||||||
click input[name="btnG"]
|
expect loadFinished true
|
||||||
expect load
|
do input[name="q"]
|
||||||
click a[href^="/url?q=https://marc.waeckerlin.org/&"]
|
this.value='Marc Wäckerlin';
|
||||||
expect load https://marc.waeckerlin.org/doku.php
|
click input[name="btnG"]
|
||||||
click a[href="/computer/index"]
|
expect loadStarted
|
||||||
expect load https://marc.waeckerlin.org/computer/index
|
expect urlChanged
|
||||||
exists h1.sectionedit1 -> Marcs Computerblog
|
expect loadFinished true
|
||||||
|
click a[href^="/url?q=https://marc.waeckerlin.org/&"]
|
||||||
|
expect loadStarted
|
||||||
|
expect urlChanged https://marc.waeckerlin.org/doku.php
|
||||||
|
expect loadFinished true
|
||||||
|
click a[href="/computer/index"]
|
||||||
|
expect loadStarted
|
||||||
|
expect urlChanged https://marc.waeckerlin.org/computer/index
|
||||||
|
expect loadFinished true
|
||||||
|
exists h1.sectionedit1 -> Marcs Computerblog
|
||||||
|
@@ -41,6 +41,7 @@ AM_CPPFLAGS="${AM_CPPFLAGS} -DQT_NO_KEYWORDS"
|
|||||||
# libraries used
|
# libraries used
|
||||||
AX_PKG_REQUIRE([mrwcxx], [mrw-c++])
|
AX_PKG_REQUIRE([mrwcxx], [mrw-c++])
|
||||||
AX_PKG_REQUIRE([xmlcxx], [libxml-cxx])
|
AX_PKG_REQUIRE([xmlcxx], [libxml-cxx])
|
||||||
|
AC_CHECK_HEADERS([cxxabi.h])
|
||||||
|
|
||||||
# create output
|
# create output
|
||||||
AC_OUTPUT
|
AC_OUTPUT
|
||||||
|
229
src/commands.hxx
229
src/commands.hxx
@@ -10,6 +10,7 @@
|
|||||||
#include <exceptions.hxx>
|
#include <exceptions.hxx>
|
||||||
#include <webpage.hxx>
|
#include <webpage.hxx>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QAuthenticator>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QWebFrame>
|
#include <QWebFrame>
|
||||||
@@ -32,6 +33,20 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <xml-cxx/xml.hxx>
|
#include <xml-cxx/xml.hxx>
|
||||||
|
|
||||||
|
#ifdef HAVE_CXXABI_H
|
||||||
|
#include <cxxabi.h>
|
||||||
|
inline QString demangle(const char* mangled) {
|
||||||
|
int status;
|
||||||
|
std::unique_ptr<char[], void (*)(void*)> result(
|
||||||
|
abi::__cxa_demangle(mangled, 0, 0, &status), free);
|
||||||
|
return QString(result.get() ? result.get() : mangled);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline QString demangle(const char* mangled) {
|
||||||
|
return QString(mangled);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
template<typename T> class optional {
|
template<typename T> class optional {
|
||||||
private:
|
private:
|
||||||
@@ -99,7 +114,7 @@ class Command: public QObject {
|
|||||||
QStringList&, QString, int, int) = 0;
|
QStringList&, QString, int, int) = 0;
|
||||||
virtual bool execute(Script*, QWebFrame*) = 0;
|
virtual bool execute(Script*, QWebFrame*) = 0;
|
||||||
static void error(Logger& log, const Exception& e) {
|
static void error(Logger& log, const Exception& e) {
|
||||||
log(QString(" FAILED: ")+e.what());
|
log(QString(" FAILED[")+demangle(typeid(e).name())+"]: "+e.what());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
void line(int linenr) {
|
void line(int linenr) {
|
||||||
@@ -187,20 +202,38 @@ class Command: public QObject {
|
|||||||
}
|
}
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
QStringList quotedStrings(QString value,
|
||||||
|
QString delimiter = " ",
|
||||||
|
bool keepDelimiters = false) {
|
||||||
|
QStringList res;
|
||||||
|
QString quot("'\"");
|
||||||
|
while (value=value.trimmed(), value.size()) {
|
||||||
|
QRegularExpression re;
|
||||||
|
int start(0);
|
||||||
|
if (quot.contains(value[0])) {
|
||||||
|
re = QRegularExpression(value[0]+" *(("+delimiter+" *)|$)");
|
||||||
|
start = 1;
|
||||||
|
} else {
|
||||||
|
re = QRegularExpression("( *"+delimiter+" *)|$");
|
||||||
|
}
|
||||||
|
int pos(value.indexOf(re, start));
|
||||||
|
if (pos<start) throw BadArgument("quote missmatch in "+value);
|
||||||
|
QRegularExpressionMatch m(re.match(value, start));
|
||||||
|
res += value.mid(start, m.capturedStart()-start);
|
||||||
|
value.remove(0, m.capturedEnd());
|
||||||
|
if (keepDelimiters && m.capturedLength()) res+=m.captured().mid(start).trimmed();
|
||||||
|
std::cout<<"REMOVE: \""<<m.captured()<<"\" 0 - "<<(m.capturedEnd()+start)
|
||||||
|
<<" start="<<start<<" pos="<<pos<<std::endl
|
||||||
|
<<"REMAINING: \""<<value<<"\""<<std::endl;
|
||||||
|
}
|
||||||
|
std::cout<<"FOUND"<<std::endl;
|
||||||
|
Q_FOREACH(QString tag, res) {
|
||||||
|
std::cout<<" - \""<<tag<<"\""<<std::endl;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
QStringList commaSeparatedList(QString value) {
|
QStringList commaSeparatedList(QString value) {
|
||||||
value=value.trimmed();
|
return quotedStrings(value, ",");
|
||||||
if (!value.size()) return QStringList();
|
|
||||||
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,
|
static QWebElement find(QWebFrame* frame, QString selector,
|
||||||
int repeat = 2, int sleepsec = 1) {
|
int repeat = 2, int sleepsec = 1) {
|
||||||
@@ -376,15 +409,15 @@ class RunDownload: public QObject {
|
|||||||
public:
|
public:
|
||||||
RunDownload(QNetworkReply* reply, QString filename):
|
RunDownload(QNetworkReply* reply, QString filename):
|
||||||
_reply(reply), _file(filename) {
|
_reply(reply), _file(filename) {
|
||||||
connect(_reply, SIGNAL(finished()), SLOT(finished()));
|
assert(connect(_reply, SIGNAL(finished()), SLOT(finished())));
|
||||||
connect(_reply, SIGNAL(downloadProgress(qint64, qint64)),
|
assert(connect(_reply, SIGNAL(downloadProgress(qint64, qint64)),
|
||||||
SLOT(downloadProgress(qint64, qint64)));
|
SLOT(downloadProgress(qint64, qint64))));
|
||||||
_file.open(QIODevice::WriteOnly);
|
_file.open(QIODevice::WriteOnly);
|
||||||
}
|
}
|
||||||
~RunDownload() {
|
~RunDownload() {
|
||||||
disconnect(_reply, SIGNAL(finished()), this, SLOT(finished()));
|
assert(disconnect(_reply, SIGNAL(finished()), this, SLOT(finished())));
|
||||||
disconnect(_reply, SIGNAL(downloadProgress(qint64, qint64)),
|
assert(disconnect(_reply, SIGNAL(downloadProgress(qint64, qint64)),
|
||||||
this, SLOT(downloadProgress(qint64, qint64)));
|
this, SLOT(downloadProgress(qint64, qint64))));
|
||||||
delete _reply;
|
delete _reply;
|
||||||
}
|
}
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
@@ -454,7 +487,8 @@ class Script: public QObject {
|
|||||||
.replace(" ", " ").replace("&", "&");
|
.replace(" ", " ").replace("&", "&");
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
Script(): _clicktype(JAVASCRIPT_CLICK), _command(0), _screenshots(true) {
|
Script(): _clicktype(JAVASCRIPT_CLICK), _command(0), _screenshots(true),
|
||||||
|
_defaultTimeout(20) {
|
||||||
initPrototypes();
|
initPrototypes();
|
||||||
}
|
}
|
||||||
Script(const Script& o):
|
Script(const Script& o):
|
||||||
@@ -535,7 +569,7 @@ class Script: public QObject {
|
|||||||
_variables.clear();
|
_variables.clear();
|
||||||
_rvariables.clear();
|
_rvariables.clear();
|
||||||
_functions.clear();
|
_functions.clear();
|
||||||
_timeout = 20;
|
_timeout = _defaultTimeout;
|
||||||
_clicktype = JAVASCRIPT_CLICK;
|
_clicktype = JAVASCRIPT_CLICK;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parseLine(QStringList& in,
|
std::shared_ptr<Command> parseLine(QStringList& in,
|
||||||
@@ -586,7 +620,7 @@ class Script: public QObject {
|
|||||||
int maxretries = 0) {
|
int maxretries = 0) {
|
||||||
bool res(true);
|
bool res(true);
|
||||||
_testsuites = testsuites;
|
_testsuites = testsuites;
|
||||||
_timeout = 20; // defaults to 20s
|
_timeout = _defaultTimeout; // defaults to 20s
|
||||||
_ignoreSignalsUntil.clear();
|
_ignoreSignalsUntil.clear();
|
||||||
addSignals(frame);
|
addSignals(frame);
|
||||||
_screenshots = screenshots;
|
_screenshots = screenshots;
|
||||||
@@ -815,6 +849,7 @@ class Script: public QObject {
|
|||||||
_variables = o._variables;
|
_variables = o._variables;
|
||||||
_rvariables = o._rvariables;
|
_rvariables = o._rvariables;
|
||||||
_timeout = o._timeout;
|
_timeout = o._timeout;
|
||||||
|
_defaultTimeout = o._timeout;
|
||||||
_clicktype = o._clicktype;
|
_clicktype = o._clicktype;
|
||||||
_testsuites = o._testsuites;
|
_testsuites = o._testsuites;
|
||||||
_testclass = o._testclass;
|
_testclass = o._testclass;
|
||||||
@@ -845,6 +880,15 @@ class Script: public QObject {
|
|||||||
void timeout(int t) {
|
void timeout(int t) {
|
||||||
_timeout = t;
|
_timeout = t;
|
||||||
}
|
}
|
||||||
|
void defaultTimeout(int t) {
|
||||||
|
_defaultTimeout = t;
|
||||||
|
}
|
||||||
|
void auth(const QString& realm, const QString& username, const QString& password) {
|
||||||
|
if (!username.isEmpty() && !password.isEmpty())
|
||||||
|
_auth[realm] = {username, password};
|
||||||
|
else if (_auth.contains(realm))
|
||||||
|
_auth.erase(_auth.find(realm));
|
||||||
|
}
|
||||||
void clicktype(ClickType c) {
|
void clicktype(ClickType c) {
|
||||||
_clicktype = c;
|
_clicktype = c;
|
||||||
}
|
}
|
||||||
@@ -876,33 +920,39 @@ class Script: public QObject {
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
void addSignals(QWebFrame* frame) {
|
void addSignals(QWebFrame* frame) {
|
||||||
connect(dynamic_cast<NetworkAccessManager*>
|
assert(connect(dynamic_cast<NetworkAccessManager*>
|
||||||
(frame->page()->networkAccessManager()),
|
(frame->page()->networkAccessManager()),
|
||||||
SIGNAL(log(QString)),
|
SIGNAL(log(QString)),
|
||||||
SLOT(log(QString)));
|
SLOT(log(QString))));
|
||||||
connect(frame, SIGNAL(contentsSizeChanged(const QSize&)),
|
assert(connect(frame->page()->networkAccessManager(),
|
||||||
SLOT(contentsSizeChanged(const QSize&)));
|
SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
|
||||||
connect(frame, SIGNAL(iconChanged()),
|
SLOT(authenticationRequired(QNetworkReply*, QAuthenticator*))));
|
||||||
SLOT(iconChanged()));
|
assert(connect(frame, SIGNAL(contentsSizeChanged(const QSize&)),
|
||||||
connect(frame, SIGNAL(initialLayoutCompleted()),
|
SLOT(contentsSizeChanged(const QSize&))));
|
||||||
SLOT(initialLayoutCompleted()));
|
assert(connect(frame, SIGNAL(iconChanged()),
|
||||||
connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
|
SLOT(iconChanged())));
|
||||||
SLOT(javaScriptWindowObjectCleared()));
|
assert(connect(frame, SIGNAL(initialLayoutCompleted()),
|
||||||
connect(frame, SIGNAL(loadFinished(bool)),
|
SLOT(initialLayoutCompleted())));
|
||||||
SLOT(loadFinished(bool)));
|
assert(connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
|
||||||
connect(frame, SIGNAL(loadStarted()),
|
SLOT(javaScriptWindowObjectCleared())));
|
||||||
SLOT(loadStarted()));
|
assert(connect(frame, SIGNAL(loadFinished(bool)),
|
||||||
connect(frame, SIGNAL(titleChanged(const QString&)),
|
SLOT(loadFinished(bool))));
|
||||||
SLOT(titleChanged(const QString&)));
|
assert(connect(frame, SIGNAL(loadStarted()),
|
||||||
connect(frame, SIGNAL(urlChanged(const QUrl&)),
|
SLOT(loadStarted())));
|
||||||
SLOT(urlChanged(const QUrl&)));
|
assert(connect(frame, SIGNAL(titleChanged(const QString&)),
|
||||||
connect(&_timer, SIGNAL(timeout()), SLOT(timeout()));
|
SLOT(titleChanged(const QString&))));
|
||||||
|
assert(connect(frame, SIGNAL(urlChanged(const QUrl&)),
|
||||||
|
SLOT(urlChanged(const QUrl&))));
|
||||||
|
assert(connect(&_timer, SIGNAL(timeout()), SLOT(timeout())));
|
||||||
}
|
}
|
||||||
void removeSignals(QWebFrame* frame) {
|
void removeSignals(QWebFrame* frame) {
|
||||||
disconnect(dynamic_cast<NetworkAccessManager*>
|
disconnect(dynamic_cast<NetworkAccessManager*>
|
||||||
(frame->page()->networkAccessManager()),
|
(frame->page()->networkAccessManager()),
|
||||||
SIGNAL(log(QString)),
|
SIGNAL(log(QString)),
|
||||||
this, SLOT(log(QString)));
|
this, SLOT(log(QString)));
|
||||||
|
disconnect(frame->page()->networkAccessManager(),
|
||||||
|
SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
|
||||||
|
this, SLOT(authenticationRequired(QNetworkReply*, QAuthenticator*)));
|
||||||
disconnect(frame, SIGNAL(contentsSizeChanged(const QSize&)),
|
disconnect(frame, SIGNAL(contentsSizeChanged(const QSize&)),
|
||||||
this, SLOT(contentsSizeChanged(const QSize&)));
|
this, SLOT(contentsSizeChanged(const QSize&)));
|
||||||
disconnect(frame, SIGNAL(iconChanged()),
|
disconnect(frame, SIGNAL(iconChanged()),
|
||||||
@@ -931,9 +981,11 @@ class Script: public QObject {
|
|||||||
for (QChar& c: text) if (c<32&&c!='\n') c='?';
|
for (QChar& c: text) if (c<32&&c!='\n') c='?';
|
||||||
if (cmd)
|
if (cmd)
|
||||||
prefix += QString("%2:%3%1 ")
|
prefix += QString("%2:%3%1 ")
|
||||||
.arg(QString(cmd->indent(), QChar(' ')))
|
.arg(QString(cmd->indent()+2, QChar(' ')))
|
||||||
.arg(cmd->file(), 20, QChar(' '))
|
.arg(cmd->file(), 20, QChar(' '))
|
||||||
.arg(cmd->line(), -4, 10, QChar(' '));
|
.arg(cmd->line(), -4, 10, QChar(' '));
|
||||||
|
else
|
||||||
|
prefix += " .... ";
|
||||||
text = prefix+text.split('\n').join("\n"+prefix+" ");
|
text = prefix+text.split('\n').join("\n"+prefix+" ");
|
||||||
logging(text);
|
logging(text);
|
||||||
std::cout<<text<<std::endl<<std::flush;
|
std::cout<<text<<std::endl<<std::flush;
|
||||||
@@ -950,7 +1002,7 @@ class Script: public QObject {
|
|||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
void error(const Exception& e) {
|
void error(const Exception& e) {
|
||||||
log(QString(" FAILED: ")+e.what());
|
log(QString(" FAILED[")+demangle(typeid(e).name())+"]: "+e.what());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> unknown(QString command) {
|
std::shared_ptr<Command> unknown(QString command) {
|
||||||
@@ -965,6 +1017,15 @@ class Script: public QObject {
|
|||||||
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
|
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
|
||||||
}
|
}
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
|
void authenticationRequired(QNetworkReply*, QAuthenticator* a) {
|
||||||
|
if (_auth.contains(a->realm())) {
|
||||||
|
log("network: login to "+a->realm());
|
||||||
|
a->setUser(_auth[a->realm()].username);
|
||||||
|
a->setPassword(_auth[a->realm()].password);
|
||||||
|
} else {
|
||||||
|
log("network: no credentials for "+a->realm());
|
||||||
|
}
|
||||||
|
}
|
||||||
void contentsSizeChanged(const QSize&) {
|
void contentsSizeChanged(const QSize&) {
|
||||||
}
|
}
|
||||||
void iconChanged() {
|
void iconChanged() {
|
||||||
@@ -1012,6 +1073,10 @@ class Script: public QObject {
|
|||||||
error(TimeOut());
|
error(TimeOut());
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
|
struct AuthRealm {
|
||||||
|
QString username;
|
||||||
|
QString password;
|
||||||
|
};
|
||||||
typedef std::map<QString, std::shared_ptr<Command>> Prototypes;
|
typedef std::map<QString, std::shared_ptr<Command>> Prototypes;
|
||||||
typedef std::vector<std::shared_ptr<Command>> Commands;
|
typedef std::vector<std::shared_ptr<Command>> Commands;
|
||||||
Prototypes _prototypes;
|
Prototypes _prototypes;
|
||||||
@@ -1028,12 +1093,14 @@ class Script: public QObject {
|
|||||||
QMap<LenString, LenString> _rvariables; ///< reverse variable mapping
|
QMap<LenString, LenString> _rvariables; ///< reverse variable mapping
|
||||||
QMap<QString, std::shared_ptr<Function> > _functions;
|
QMap<QString, std::shared_ptr<Function> > _functions;
|
||||||
int _timeout;
|
int _timeout;
|
||||||
|
int _defaultTimeout;
|
||||||
ClickType _clicktype;
|
ClickType _clicktype;
|
||||||
QString _targetdir;
|
QString _targetdir;
|
||||||
std::shared_ptr<xml::Node> _testsuites; ///< only valid within run
|
std::shared_ptr<xml::Node> _testsuites; ///< only valid within run
|
||||||
QString _testclass;
|
QString _testclass;
|
||||||
Command* _command;
|
Command* _command;
|
||||||
QString _path;
|
QString _path;
|
||||||
|
QMap<QString, AuthRealm> _auth;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Do: public Command {
|
class Do: public Command {
|
||||||
@@ -1587,8 +1654,8 @@ class Download: public Command {
|
|||||||
_realfilename = script->replacevars(_filename);
|
_realfilename = script->replacevars(_filename);
|
||||||
log("REALFILENAME="+_realfilename);
|
log("REALFILENAME="+_realfilename);
|
||||||
frame->page()->setForwardUnsupportedContent(true);
|
frame->page()->setForwardUnsupportedContent(true);
|
||||||
connect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
|
assert(connect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||||
this, SLOT(unsupportedContent(QNetworkReply*)));
|
this, SLOT(unsupportedContent(QNetworkReply*))));
|
||||||
try {
|
try {
|
||||||
bool res(_next->execute(script, frame)); // start download
|
bool res(_next->execute(script, frame)); // start download
|
||||||
script->timer().stop(); // no timeout during download
|
script->timer().stop(); // no timeout during download
|
||||||
@@ -1626,8 +1693,8 @@ class Download: public Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connect(new RunDownload(reply, _realfilename),
|
assert(connect(new RunDownload(reply, _realfilename),
|
||||||
SIGNAL(completed(bool, bool)), SLOT(completed(bool, bool)));
|
SIGNAL(completed(bool, bool)), SLOT(completed(bool, bool))));
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
QString _filename;
|
QString _filename;
|
||||||
@@ -2312,14 +2379,22 @@ class Check: public Command {
|
|||||||
int indent) {
|
int indent) {
|
||||||
std::shared_ptr<Check> cmd(new Check());
|
std::shared_ptr<Check> cmd(new Check());
|
||||||
cmd->_next = 0;
|
cmd->_next = 0;
|
||||||
int pos(args.indexOf(QRegularExpression("[=!.^~<>]")));
|
QString comp("[=!.^~<>]");
|
||||||
if (pos<0) throw BadArgument(tag()+" needs a comparision, not: "+args);
|
QStringList allargs = quotedStrings(args, comp, true);
|
||||||
cmd->_value1 = args.left(pos).trimmed();
|
if (allargs.size()<2 || allargs[1].size()!=1 ||
|
||||||
cmd->_cmp = args[pos].toLatin1();
|
!QRegularExpression("^"+comp+"$").match(allargs[1]).hasMatch())
|
||||||
cmd->_value2 = args.mid(pos+1).trimmed();
|
throw BadArgument(tag()+" needs a comparision, not: "+args);
|
||||||
|
if (allargs.size()>3)
|
||||||
|
throw BadArgument(tag()+" has at most three arguments");
|
||||||
|
cmd->_value1 = allargs[0];
|
||||||
|
cmd->_cmp = allargs[1][0].toLatin1();
|
||||||
|
if (allargs.size()==3) {
|
||||||
|
cmd->_value2 = allargs[2];
|
||||||
|
} else {
|
||||||
if (in.size() && in.first().contains(QRegularExpression("^ "))) {
|
if (in.size() && in.first().contains(QRegularExpression("^ "))) {
|
||||||
cmd->_next = script->parseLine(in, file, line+1, indent+1);
|
cmd->_next = script->parseLine(in, file, line+1, indent+1);
|
||||||
cmd->_next->log(false); // suppress logging of subcommand
|
cmd->_next->log(false); // suppress logging of subcommand
|
||||||
|
} else throw BadArgument(tag()+" needs a third argument or a following command");
|
||||||
}
|
}
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
@@ -2704,6 +2779,49 @@ class Fail: public Command {
|
|||||||
QString _text;
|
QString _text;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Auth: public Command {
|
||||||
|
public:
|
||||||
|
QString tag() const {
|
||||||
|
return "auth";
|
||||||
|
}
|
||||||
|
QString description() const {
|
||||||
|
return
|
||||||
|
tag()+" <realm> <username> <password>"
|
||||||
|
"\n\n"+
|
||||||
|
tag()+" <realm>"
|
||||||
|
"\n\n"
|
||||||
|
"Set basic authentication credentials for <realm> to"
|
||||||
|
" <username> and <password>. If no realm is given,"
|
||||||
|
" the credentials for the given realm are removed.";
|
||||||
|
}
|
||||||
|
QString command() const {
|
||||||
|
return tag()+" "+_username+" "+_password;
|
||||||
|
}
|
||||||
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
|
QStringList&, QString, int, int) {
|
||||||
|
std::shared_ptr<Auth> cmd(new Auth());
|
||||||
|
QStringList allargs = args.split(" ");
|
||||||
|
if (!allargs.size()) throw BadArgument("requires at least a <realm>");
|
||||||
|
cmd->_realm = allargs.takeFirst();
|
||||||
|
if (allargs.size() && allargs.size()==2) {
|
||||||
|
cmd->_username=allargs[0];
|
||||||
|
cmd->_password=allargs[1];
|
||||||
|
} else {
|
||||||
|
throw BadArgument(QString("requires <username> and <password>, but %1 was given")
|
||||||
|
.arg(args));
|
||||||
|
}
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
bool execute(Script* script, QWebFrame* frame) {
|
||||||
|
Logger log(this, script);
|
||||||
|
script->auth(_realm, _username, _password);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
QString _realm;
|
||||||
|
QString _username;
|
||||||
|
QString _password;
|
||||||
|
};
|
||||||
|
|
||||||
/* Template:
|
/* Template:
|
||||||
class : public Command {
|
class : public Command {
|
||||||
@@ -2786,8 +2904,8 @@ inline bool Command::runScript(Logger& log, Command* parentCommand,
|
|||||||
scriptCopy.set(*var, parent->replacevars(*arg));
|
scriptCopy.set(*var, parent->replacevars(*arg));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
connect(&scriptCopy, SIGNAL(logging(QString)),
|
assert(connect(&scriptCopy, SIGNAL(logging(QString)),
|
||||||
parent, SLOT(parentlog(QString)));
|
parent, SLOT(parentlog(QString))));
|
||||||
parent->removeSignals(frame);
|
parent->removeSignals(frame);
|
||||||
bool res(scriptCopy.run(frame));
|
bool res(scriptCopy.run(frame));
|
||||||
parent->addSignals(frame);
|
parent->addSignals(frame);
|
||||||
@@ -2845,6 +2963,7 @@ inline void Script::initPrototypes() {
|
|||||||
add(new Include);
|
add(new Include);
|
||||||
add(new Case);
|
add(new Case);
|
||||||
add(new Fail);
|
add(new Fail);
|
||||||
|
add(new Auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -60,13 +60,13 @@ class NetworkAccessManager: public QNetworkAccessManager {
|
|||||||
return QNetworkAccessManager::configuration();
|
return QNetworkAccessManager::configuration();
|
||||||
}
|
}
|
||||||
void connectToHost(const QString& hostName, quint16 port = 80) {
|
void connectToHost(const QString& hostName, quint16 port = 80) {
|
||||||
//log(__PRETTY_FUNCTION__);
|
//log(__PRETTY_FUNCTION__ + QString(" -> ") + hostName);
|
||||||
QNetworkAccessManager::connectToHost(hostName, port);
|
QNetworkAccessManager::connectToHost(hostName, port);
|
||||||
}
|
}
|
||||||
void connectToHostEncrypted(const QString& hostName, quint16 port = 443,
|
void connectToHostEncrypted(const QString& hostName, quint16 port = 443,
|
||||||
const QSslConfiguration& sslConfiguration
|
const QSslConfiguration& sslConfiguration
|
||||||
= QSslConfiguration::defaultConfiguration()) {
|
= QSslConfiguration::defaultConfiguration()) {
|
||||||
//log(__PRETTY_FUNCTION__);
|
//log(__PRETTY_FUNCTION__ + QString(" -> ") + hostName);
|
||||||
QNetworkAccessManager::connectToHostEncrypted(hostName, port,
|
QNetworkAccessManager::connectToHostEncrypted(hostName, port,
|
||||||
sslConfiguration);
|
sslConfiguration);
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ class NetworkAccessManager: public QNetworkAccessManager {
|
|||||||
virtual QNetworkReply* createRequest(Operation op,
|
virtual QNetworkReply* createRequest(Operation op,
|
||||||
const QNetworkRequest& req,
|
const QNetworkRequest& req,
|
||||||
QIODevice* outgoingData = 0) {
|
QIODevice* outgoingData = 0) {
|
||||||
//log(__PRETTY_FUNCTION__);
|
//log(__PRETTY_FUNCTION__ + QString(" -> ") + req.url().url());
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case QNetworkAccessManager::HeadOperation: break;
|
case QNetworkAccessManager::HeadOperation: break;
|
||||||
case QNetworkAccessManager::GetOperation: break;
|
case QNetworkAccessManager::GetOperation: break;
|
||||||
|
@@ -195,6 +195,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
//std::cout<<"titleChanged: "<<title.toStdString()<<std::endl;
|
//std::cout<<"titleChanged: "<<title.toStdString()<<std::endl;
|
||||||
}
|
}
|
||||||
void on__web_urlChanged(const QUrl& url) {
|
void on__web_urlChanged(const QUrl& url) {
|
||||||
|
_url->setText(url.url());
|
||||||
enterText(true);
|
enterText(true);
|
||||||
if (_record->isChecked())
|
if (_record->isChecked())
|
||||||
appendCommand("expect "+map("urlChanged "+url.url()));
|
appendCommand("expect "+map("urlChanged "+url.url()));
|
||||||
|
@@ -115,6 +115,10 @@ int main(int argc, char *argv[]) try {
|
|||||||
(QStringList()<<"r"<<"retries",
|
(QStringList()<<"r"<<"retries",
|
||||||
"on error retry up to <maxretries> times",
|
"on error retry up to <maxretries> times",
|
||||||
"maxretries", "0"));
|
"maxretries", "0"));
|
||||||
|
parser.addOption(QCommandLineOption
|
||||||
|
(QStringList()<<"timeout",
|
||||||
|
"set default timeout in seconds",
|
||||||
|
"timeout", "40"));
|
||||||
parser.addOption(QCommandLineOption
|
parser.addOption(QCommandLineOption
|
||||||
(QStringList()<<"W"<<"width",
|
(QStringList()<<"W"<<"width",
|
||||||
"set screenshot size to <width> pixel", "width", "2048"));
|
"set screenshot size to <width> pixel", "width", "2048"));
|
||||||
@@ -141,8 +145,10 @@ int main(int argc, char *argv[]) try {
|
|||||||
}
|
}
|
||||||
if (parser.isSet("path")) script.path(parser.value("path"));
|
if (parser.isSet("path")) script.path(parser.value("path"));
|
||||||
int retries(parser.value("retries").toInt());
|
int retries(parser.value("retries").toInt());
|
||||||
|
int timeout(parser.value("timeout").toInt());
|
||||||
int width(parser.value("width").toInt());
|
int width(parser.value("width").toInt());
|
||||||
int height(parser.value("height").toInt());
|
int height(parser.value("height").toInt());
|
||||||
|
script.defaultTimeout(parser.value("timeout").toInt());
|
||||||
QString target(parser.value("target-path"));
|
QString target(parser.value("target-path"));
|
||||||
p.resize(width, height);
|
p.resize(width, height);
|
||||||
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuites"));
|
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuites"));
|
||||||
|
Reference in New Issue
Block a user