initial sources
This commit is contained in:
1466
src/commands.hxx
Normal file
1466
src/commands.hxx
Normal file
@@ -0,0 +1,1466 @@
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
#ifndef COMMANDS_HXX
|
||||
#define COMMANDS_HXX
|
||||
|
||||
#include <exceptions.hxx>
|
||||
#include <webpage.hxx>
|
||||
#include <QNetworkReply>
|
||||
#include <QCoreApplication>
|
||||
#include <QStringList>
|
||||
#include <QWebFrame>
|
||||
#include <QWebView>
|
||||
#include <QWebElement>
|
||||
#include <QPainter>
|
||||
#include <QImage>
|
||||
#include <QTimer>
|
||||
#include <QProcess>
|
||||
#include <QMouseEvent>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#include <xml-cxx/xml.hxx>
|
||||
|
||||
class Script;
|
||||
class Command;
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
Logger(Command* command, Script* script);
|
||||
void plainlog(QString txt);
|
||||
void log(QString txt);
|
||||
~Logger();
|
||||
private:
|
||||
Command* _command;
|
||||
Script* _script;
|
||||
};
|
||||
|
||||
class Command: public QObject {
|
||||
Q_OBJECT;
|
||||
public:
|
||||
Command(): _log(true), _line(-1) {}
|
||||
virtual ~Command() {}
|
||||
virtual QString tag() const = 0;
|
||||
virtual QString description() const = 0;
|
||||
virtual QString command() const = 0;
|
||||
virtual std::shared_ptr<Command> parse(Script*, QString,
|
||||
QStringList&, int) = 0;
|
||||
virtual bool execute(Script*, QWebFrame*) = 0;
|
||||
void line(int linenr) {
|
||||
_line = linenr;
|
||||
}
|
||||
int line() const {
|
||||
return _line;
|
||||
}
|
||||
void testsuite(QString name) {
|
||||
_testsuite = name;
|
||||
}
|
||||
QString testsuite() {
|
||||
return _testsuite;
|
||||
}
|
||||
void targetdir(QString name) {
|
||||
_targetdir = name;
|
||||
}
|
||||
QString targetdir() {
|
||||
return _targetdir;
|
||||
}
|
||||
bool log() {
|
||||
return _log;
|
||||
}
|
||||
void log(bool l) {
|
||||
_log = l;
|
||||
}
|
||||
QString result() {
|
||||
return _result;
|
||||
}
|
||||
protected:
|
||||
void sleep(int s) {
|
||||
QTime dieTime= QTime::currentTime().addSecs(s);
|
||||
while (QTime::currentTime()<dieTime)
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
}
|
||||
QWebElement find(QWebFrame* frame, QString selector, Logger& log,
|
||||
int repeat = 2, int sleepsec = 1) {
|
||||
QWebElement element;
|
||||
element = find1(frame, selector, log, repeat, sleepsec);
|
||||
if (!element.isNull()) return element;
|
||||
element = find1(frame->page()->currentFrame(), selector, log, repeat, sleepsec);
|
||||
if (!element.isNull()) return element;
|
||||
element = find1(frame->page()->mainFrame(), selector, log, repeat, sleepsec);
|
||||
if (!element.isNull()) return element;
|
||||
return element;
|
||||
}
|
||||
QWebElement find1(QWebFrame* frame, QString selector, Logger& log,
|
||||
int repeat = 5, int sleepsec = 1) {
|
||||
QWebElement element;
|
||||
for (int i=0; i<repeat; ++i) {
|
||||
element = frame->findFirstElement(selector);
|
||||
if (!element.isNull()) return element;
|
||||
Q_FOREACH(QWebFrame* childFrame, frame->childFrames()) {
|
||||
element = find1(childFrame, selector, log, 1, 0);
|
||||
if (!element.isNull()) return element;
|
||||
}
|
||||
if (sleepsec) sleep(sleepsec);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
void realMouseClick(QWebFrame* frame, QString selector, Logger& log) {
|
||||
QWebElement element(find(frame, selector, log));
|
||||
if (element.isNull()) throw ElementNotFound(selector);
|
||||
QWidget* web(frame->page()->view());
|
||||
QRect elGeom=element.geometry();
|
||||
QPoint elPoint=elGeom.center();
|
||||
int elX=elPoint.x();
|
||||
int elY=elPoint.y();
|
||||
int webWidth=web->width();
|
||||
int webHeight=web->height();
|
||||
int pixelsToScrolRight=0;
|
||||
int pixelsToScrolDown=0;
|
||||
if (elX>webWidth)
|
||||
pixelsToScrolRight = //the +10 scrolls a bit further
|
||||
elX-webWidth+elGeom.width()/2+10;
|
||||
if (elY>webHeight)
|
||||
pixelsToScrolDown = //the +10 scrolls a bit further
|
||||
elY-webHeight+elGeom.height()/2+10;
|
||||
frame->setScrollBarValue(Qt::Horizontal, pixelsToScrolRight);
|
||||
frame->setScrollBarValue(Qt::Vertical, pixelsToScrolDown);
|
||||
QPoint pointToClick(elX-pixelsToScrolRight, elY-pixelsToScrolDown);
|
||||
QMouseEvent pressEvent(QMouseEvent::MouseButtonPress,
|
||||
pointToClick, Qt::LeftButton, Qt::LeftButton,
|
||||
Qt::NoModifier);
|
||||
QCoreApplication::sendEvent(web, &pressEvent);
|
||||
QMouseEvent releaseEvent(QMouseEvent::MouseButtonRelease,
|
||||
pointToClick, Qt::LeftButton, Qt::LeftButton,
|
||||
Qt::NoModifier);
|
||||
QCoreApplication::sendEvent(web, &releaseEvent);
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
void log(Script*);
|
||||
bool _log;
|
||||
protected:
|
||||
QString _result;
|
||||
private:
|
||||
int _line;
|
||||
QString _testsuite;
|
||||
QString _targetdir;
|
||||
};
|
||||
|
||||
class Empty: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
""
|
||||
"\n\n"
|
||||
"Empty lines are allowed";
|
||||
}
|
||||
QString command() const {
|
||||
return "";
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
||||
std::shared_ptr<Empty> cmd(new Empty());
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script*, QWebFrame*) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class Comment: public Command {
|
||||
public:
|
||||
Comment(QString line): _line(line) {}
|
||||
QString tag() const {
|
||||
return "#";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"# comment"
|
||||
"\n\n"
|
||||
"Comments are lines that start with #";
|
||||
}
|
||||
QString command() const {
|
||||
return _line;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Comment> cmd(new Comment(args));
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _line;
|
||||
};
|
||||
|
||||
class Screenshot: public Command {
|
||||
public:
|
||||
static QString sourceHtml(int line, QString target, QString base,
|
||||
QString name, QWebFrame* frame) {
|
||||
if (!QDir(target).exists() && !QDir().mkpath(target))
|
||||
throw DirectoryCannotBeCreated(target);
|
||||
QCoreApplication::processEvents();
|
||||
QString filename(target+QDir::separator()+
|
||||
QString("%1-%2-%3.html")
|
||||
.arg(base)
|
||||
.arg(line, 4, 10, QChar('0')).arg(name));
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
throw CannotWriteSouceHTML(filename);
|
||||
QTextStream out(&file);
|
||||
out<<frame->toHtml();
|
||||
if (out.status()!=QTextStream::Ok) throw CannotWriteSouceHTML(filename);
|
||||
return QDir(filename).absolutePath();
|
||||
}
|
||||
static QString screenshot(int line, QString target, QString base,
|
||||
QString name, QWebFrame* frame) {
|
||||
bool wasShown(frame->page()->view()->isVisible());
|
||||
frame->page()->view()->show();
|
||||
QCoreApplication::processEvents();
|
||||
QImage image(frame->page()->view()->size(), QImage::Format_RGB32);
|
||||
QPainter painter(&image);
|
||||
frame->render(&painter);
|
||||
painter.end();
|
||||
if (!wasShown) frame->page()->view()->hide();
|
||||
if (!QDir(target).exists() && !QDir().mkpath(target))
|
||||
throw DirectoryCannotBeCreated(target);
|
||||
QCoreApplication::processEvents();
|
||||
QString filename(target+QDir::separator()+
|
||||
QString("%1-%2-%3.png")
|
||||
.arg(base)
|
||||
.arg(line, 4, 10, QChar('0')).arg(name));
|
||||
if (!image.save(filename)) throw CannotWriteScreenshot(filename);
|
||||
return QDir(filename).absolutePath();
|
||||
}
|
||||
QString tag() const {
|
||||
return "screenshot";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"screenshot <filename-base>"
|
||||
"\n\n"
|
||||
"Create a PNG screenshot of the actual web page and store it in the "
|
||||
"file <filename-base>.png. If not already opened, a browser window "
|
||||
"will pop up to take the screenshot.";
|
||||
}
|
||||
QString command() const {
|
||||
return "screenshot "+_filename;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Screenshot> cmd(new Screenshot());
|
||||
cmd->_filename = args;
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame);
|
||||
private:
|
||||
QString _filename;
|
||||
};
|
||||
|
||||
class RunDownload: public QObject {
|
||||
Q_OBJECT;
|
||||
public:
|
||||
RunDownload(QNetworkReply* reply, QString filename):
|
||||
_reply(reply), _file(filename) {
|
||||
connect(_reply, SIGNAL(finished()), SLOT(finished()));
|
||||
connect(_reply, SIGNAL(downloadProgress(qint64, qint64)),
|
||||
SLOT(downloadProgress(qint64, qint64)));
|
||||
_file.open(QIODevice::WriteOnly);
|
||||
}
|
||||
~RunDownload() {
|
||||
disconnect(_reply, SIGNAL(finished()), this, SLOT(finished()));
|
||||
disconnect(_reply, SIGNAL(downloadProgress(qint64, qint64)),
|
||||
this, SLOT(downloadProgress(qint64, qint64)));
|
||||
delete _reply;
|
||||
}
|
||||
Q_SIGNALS:
|
||||
void completed(bool netsuccess, bool filesuccess);
|
||||
private Q_SLOTS:
|
||||
void finished() {
|
||||
_file.write(_reply->readAll());
|
||||
_file.close();
|
||||
completed(_reply->error()==QNetworkReply::NoError,
|
||||
_file.error()==QFileDevice::NoError);
|
||||
delete this;
|
||||
}
|
||||
void downloadProgress(qint64 bytes, qint64) {
|
||||
_file.write(_reply->read(bytes));
|
||||
}
|
||||
private:
|
||||
QNetworkReply* _reply;
|
||||
QFile _file;
|
||||
};
|
||||
|
||||
/** @mainpage
|
||||
|
||||
The WebTester web application testing framework consists of three parts:
|
||||
|
||||
-# a gui to create testcases, see @ref webtester
|
||||
-# a command line tool to run testcases, see @ref webrunner
|
||||
-# test scripts, see @ref testscript. */
|
||||
|
||||
class Script: public QObject {
|
||||
Q_OBJECT;
|
||||
Q_SIGNALS:
|
||||
void logging(QString);
|
||||
public:
|
||||
typedef std::pair<QString, QStringList> Signal;
|
||||
public:
|
||||
static QString xmlattr(QString attr, bool br = false) {
|
||||
attr.replace("&", "&")//.replace(" ", " ")
|
||||
.replace("\"", """);
|
||||
if (br) attr.replace("\n", "<br/>");
|
||||
attr.replace("<", "<").replace(">", ">");
|
||||
return attr;
|
||||
}
|
||||
static QString xmlstr(const std::string& attr) {
|
||||
return xmlstr(QString::fromStdString(attr));
|
||||
}
|
||||
static QString xmlstr(QString attr) {
|
||||
return attr
|
||||
.replace(">", ">").replace("<", "<")
|
||||
.replace("<br/>", "\n").replace(""", "\"")
|
||||
.replace(" ", " ").replace("&", "&");
|
||||
}
|
||||
public:
|
||||
Script() {
|
||||
initPrototypes();
|
||||
}
|
||||
QString syntax() const {
|
||||
return
|
||||
"Script syntax is a text file that consists of list of commands. Each "
|
||||
"command starts at the begin of a new line. Empty lines are allowed. "
|
||||
"Lines that start with \"#\" are treated as comments."
|
||||
"\n\n"
|
||||
"Note: When a selector is required as parameter, then the selector "
|
||||
"is a CSS selector that must not contain spaces.";
|
||||
}
|
||||
QString commands() const {
|
||||
QString cmds;
|
||||
for (auto it(_prototypes.begin()); it!=_prototypes.end(); ++it)
|
||||
cmds+="\n\n"+it->second->description();
|
||||
return cmds.trimmed();
|
||||
}
|
||||
void reset() {
|
||||
_script.clear();
|
||||
while (!_signals.empty()) _signals.pop();
|
||||
_timer.stop();
|
||||
_ignores.clear();
|
||||
_cout.clear();
|
||||
_cerr.clear();
|
||||
}
|
||||
std::shared_ptr<Command> parse(QStringList& in, int linenr) try {
|
||||
QString line(in.takeFirst().trimmed());
|
||||
QString cmd(line), args;
|
||||
int space(line.indexOf(' '));
|
||||
if (space>0) {
|
||||
cmd = line.left(space);
|
||||
args = line.right(line.size()-space-1);
|
||||
}
|
||||
Prototypes::const_iterator it(_prototypes.find(cmd));
|
||||
if (it!=_prototypes.end()) {
|
||||
std::shared_ptr<Command> command(it->second->parse
|
||||
(this, args, in, linenr));
|
||||
command->line(linenr);
|
||||
return command;
|
||||
} else {
|
||||
return unknown(line);
|
||||
}
|
||||
} catch (Exception& e) {
|
||||
e.line(linenr);
|
||||
throw;
|
||||
}
|
||||
void parse(QStringList in) {
|
||||
for (int linenr(1), oldsize(0);
|
||||
oldsize=in.size(), in.size();
|
||||
linenr+=oldsize-in.size())
|
||||
_script.push_back(parse(in, linenr));
|
||||
}
|
||||
void run(QWebFrame* frame, xml::Node& testsuite,
|
||||
QString targetdir = QString(), bool screenshots = true,
|
||||
int maxretries = 0) {
|
||||
_timeout = 20; // defaults to 20s
|
||||
_ignoreSignalsUntil.clear();
|
||||
addSignals(frame);
|
||||
_screenshots = screenshots;
|
||||
_timer.setSingleShot(true);
|
||||
int retries(0), back(0);
|
||||
for (auto cmd(_script.begin()); cmd!=_script.end(); ++cmd) {
|
||||
xml::Node testcase("testcase");
|
||||
try {
|
||||
testcase.attr("classname") =
|
||||
testsuite.attr("name");
|
||||
//xmlattr((*cmd)->command(), true).toStdString();
|
||||
testcase.attr("name") =
|
||||
xmlattr((*cmd)->tag(), true).toStdString();
|
||||
if (!_ignores.size() || (*cmd)->tag()=="label") { // not ignored
|
||||
_timer.start(_timeout*1000);
|
||||
(*cmd)->testsuite(xmlstr(testsuite.attr("name")));
|
||||
(*cmd)->targetdir(!targetdir.isEmpty() ? targetdir :
|
||||
xmlstr(testsuite.attr("name")));
|
||||
try {
|
||||
if (!(*cmd)->execute(this, frame)) {
|
||||
_timer.stop();
|
||||
if (!back) retries = 0; else --back;
|
||||
testcase<<(xml::String("system-out") =
|
||||
xmlattr(_cout).toStdString());
|
||||
testcase<<(xml::String("system-err") =
|
||||
xmlattr(_cerr).toStdString());
|
||||
_cout.clear();
|
||||
_cerr.clear();
|
||||
testsuite<<testcase;
|
||||
break; // test is successfully finished
|
||||
}
|
||||
} catch (PossibleRetryLoad& e) {
|
||||
_timer.stop();
|
||||
// timeout may happen during load due to bad internet connection
|
||||
if (screenshots)
|
||||
try { // take a screenshot on error
|
||||
QString filename(Screenshot::screenshot
|
||||
((*cmd)->line(), (*cmd)->targetdir(),
|
||||
QFileInfo((*cmd)->testsuite()).baseName(),
|
||||
QString("retry-%1")
|
||||
.arg((ulong)retries, 2, 10,
|
||||
QLatin1Char('0')),
|
||||
frame));
|
||||
plainlog("[[ATTACHMENT|"+filename+"]]");
|
||||
} catch (... ) {} // ignore exception in screenshot
|
||||
if (++retries<=maxretries) { // retry in that case
|
||||
QUrl url(frame->url());
|
||||
if ((*cmd)->command()=="expect loadFinished true") {
|
||||
------cmd;
|
||||
back += 3;
|
||||
_ignoreSignalsUntil = "loadStarted";
|
||||
frame->load(url);
|
||||
} else if ((*cmd)->command()=="expect loadStarted") {
|
||||
----cmd;
|
||||
back += 2;
|
||||
_ignoreSignalsUntil = "loadStarted";
|
||||
frame->page()->triggerAction(QWebPage::Stop);
|
||||
} else if ((*cmd)->command().startsWith("expect urlChanged")) {
|
||||
QString url2((*cmd)->command());
|
||||
url2.remove("expect urlChanged");
|
||||
if (url2.size()) url=url2.trimmed();
|
||||
----cmd;
|
||||
back += 2;
|
||||
_ignoreSignalsUntil = "loadStarted";
|
||||
frame->load(url);
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
log(QString("WARNING: retry#%1, redo last %2 steps; error: %3")
|
||||
.arg(retries).arg(back).arg(e.what()));
|
||||
}
|
||||
_timer.stop();
|
||||
if (!back) retries = 0; else --back;
|
||||
testcase<<(xml::String("system-out") =
|
||||
xmlattr(_cout).toStdString());
|
||||
testcase<<(xml::String("system-err") =
|
||||
xmlattr(_cerr).toStdString());
|
||||
_cout.clear();
|
||||
_cerr.clear();
|
||||
testsuite<<testcase;
|
||||
}
|
||||
} catch (Exception& e) {
|
||||
_timer.stop();
|
||||
if (!back) retries = 0; else --back;
|
||||
testcase<<(xml::String("system-out") =
|
||||
xmlattr(_cout).toStdString());
|
||||
testcase<<(xml::String("system-err") =
|
||||
xmlattr(_cerr).toStdString());
|
||||
_cout.clear();
|
||||
_cerr.clear();
|
||||
testsuite<<testcase;
|
||||
removeSignals(frame);
|
||||
e.line((*cmd)->line());
|
||||
if (screenshots)
|
||||
try { // write html source and take a last screenshot on error
|
||||
{
|
||||
QString filename(Screenshot::sourceHtml
|
||||
((*cmd)->line(), (*cmd)->targetdir(),
|
||||
QFileInfo((*cmd)->testsuite()).baseName(),
|
||||
"error", frame));
|
||||
plainlog("[[ATTACHMENT|"+filename+"]]");
|
||||
} {
|
||||
QString filename(Screenshot::screenshot
|
||||
((*cmd)->line(), (*cmd)->targetdir(),
|
||||
QFileInfo((*cmd)->testsuite()).baseName(),
|
||||
"error", frame));
|
||||
plainlog("[[ATTACHMENT|"+filename+"]]");
|
||||
}
|
||||
} catch (... ) {} // ignore exception in screenshot
|
||||
throw;
|
||||
}
|
||||
}
|
||||
removeSignals(frame);
|
||||
if (!_signals.empty()) throw UnhandledSignals(_signals);
|
||||
}
|
||||
QString& cout() {
|
||||
return _cout;
|
||||
}
|
||||
QString& cerr() {
|
||||
return _cerr;
|
||||
}
|
||||
int steps() {
|
||||
return _script.size();
|
||||
}
|
||||
bool screenshots() {
|
||||
return _screenshots;
|
||||
}
|
||||
Signal getSignal() {
|
||||
while (!_signals.size()) QCoreApplication::processEvents();
|
||||
Signal res(_signals.front());
|
||||
_signals.pop();
|
||||
return res;
|
||||
}
|
||||
QTimer& timer() {
|
||||
return _timer;
|
||||
}
|
||||
void ignoreto(const QString& l) {
|
||||
_ignores.insert(l);
|
||||
}
|
||||
void label(const QString& l) {
|
||||
_ignores.remove(l);
|
||||
}
|
||||
void set(QString name, QString value) {
|
||||
_variables[name] = value;
|
||||
}
|
||||
void unset(QString name) {
|
||||
_variables.remove(name);
|
||||
}
|
||||
void timeout(int t) {
|
||||
_timeout = t;
|
||||
}
|
||||
QString replacevars(QString txt) {
|
||||
for(QMap<QString, QString>::iterator it(_variables.begin());
|
||||
it!=_variables.end(); ++it)
|
||||
txt.replace(it.key(), it.value());
|
||||
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()),
|
||||
SIGNAL(log(QString)),
|
||||
SLOT(log(QString)));
|
||||
connect(frame, SIGNAL(contentsSizeChanged(const QSize&)),
|
||||
SLOT(contentsSizeChanged(const QSize&)));
|
||||
connect(frame, SIGNAL(iconChanged()),
|
||||
SLOT(iconChanged()));
|
||||
connect(frame, SIGNAL(initialLayoutCompleted()),
|
||||
SLOT(initialLayoutCompleted()));
|
||||
connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
|
||||
SLOT(javaScriptWindowObjectCleared()));
|
||||
connect(frame, SIGNAL(loadFinished(bool)),
|
||||
SLOT(loadFinished(bool)));
|
||||
connect(frame, SIGNAL(loadStarted()),
|
||||
SLOT(loadStarted()));
|
||||
connect(frame, SIGNAL(titleChanged(const QString&)),
|
||||
SLOT(titleChanged(const QString&)));
|
||||
connect(frame, SIGNAL(urlChanged(const QUrl&)),
|
||||
SLOT(urlChanged(const QUrl&)));
|
||||
connect(&_timer, SIGNAL(timeout()), SLOT(timeout()));
|
||||
}
|
||||
void removeSignals(QWebFrame* frame) {
|
||||
disconnect(dynamic_cast<NetworkAccessManager*>
|
||||
(frame->page()->networkAccessManager()),
|
||||
SIGNAL(log(QString)),
|
||||
this, SLOT(log(QString)));
|
||||
disconnect(frame, SIGNAL(contentsSizeChanged(const QSize&)),
|
||||
this, SLOT(contentsSizeChanged(const QSize&)));
|
||||
disconnect(frame, SIGNAL(iconChanged()),
|
||||
this, SLOT(iconChanged()));
|
||||
disconnect(frame, SIGNAL(initialLayoutCompleted()),
|
||||
this, SLOT(initialLayoutCompleted()));
|
||||
disconnect(frame, SIGNAL(javaScriptWindowObjectCleared()),
|
||||
this, SLOT(javaScriptWindowObjectCleared()));
|
||||
disconnect(frame, SIGNAL(loadFinished(bool)),
|
||||
this, SLOT(loadFinished(bool)));
|
||||
disconnect(frame, SIGNAL(loadStarted()),
|
||||
this, SLOT(loadStarted()));
|
||||
disconnect(frame, SIGNAL(titleChanged(const QString&)),
|
||||
this, SLOT(titleChanged(const QString&)));
|
||||
disconnect(frame, SIGNAL(urlChanged(const QUrl&)),
|
||||
this, SLOT(urlChanged(const QUrl&)));
|
||||
disconnect(frame, SIGNAL(urlChanged(const QUrl&)),
|
||||
this, SLOT(urlChanged(const QUrl&)));
|
||||
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(timeout()));
|
||||
}
|
||||
void initPrototypes();
|
||||
void add(Command* c) {
|
||||
_prototypes[c->tag()] = std::shared_ptr<Command>(c);
|
||||
}
|
||||
private Q_SLOTS:
|
||||
void contentsSizeChanged(const QSize&) {
|
||||
}
|
||||
void iconChanged() {
|
||||
}
|
||||
void initialLayoutCompleted() {
|
||||
}
|
||||
void javaScriptWindowObjectCleared() {
|
||||
}
|
||||
void loadFinished(bool ok) {
|
||||
QString sig(ok?"true":"false");
|
||||
if (_ignoreSignalsUntil.size() &&
|
||||
_ignoreSignalsUntil != "loadFinished "+sig) {
|
||||
log("warning: ignored loadFinished, waiting for "+_ignoreSignalsUntil);
|
||||
return;
|
||||
}
|
||||
_ignoreSignalsUntil.clear();
|
||||
log(".... signal");
|
||||
log("received loadFinished "+QString(ok?"true":"false"));
|
||||
log(".....................");
|
||||
_signals.push(std::make_pair("loadFinished", QStringList(sig)));
|
||||
}
|
||||
void loadStarted() {
|
||||
if (_ignoreSignalsUntil.size() && _ignoreSignalsUntil != "loadStarted") {
|
||||
log("warning: ignored loadStarted, waiting for "+_ignoreSignalsUntil);
|
||||
return;
|
||||
}
|
||||
_ignoreSignalsUntil.clear();
|
||||
log(".... signal");
|
||||
log("received loadStarted");
|
||||
log(".....................");
|
||||
_signals.push(std::make_pair("loadStarted", QStringList()));
|
||||
}
|
||||
void frameChanged() {
|
||||
}
|
||||
void titleChanged(const QString&) {
|
||||
//_signals.push(std::make_pair("titleChanged", QStringList(title)));
|
||||
}
|
||||
void urlChanged(const QUrl& url) {
|
||||
if (_ignoreSignalsUntil.size() && _ignoreSignalsUntil != "urlChanged") {
|
||||
log("warning: ignored urlChanged, waiting for "+_ignoreSignalsUntil);
|
||||
return;
|
||||
}
|
||||
_ignoreSignalsUntil.clear();
|
||||
log(".... signal");
|
||||
log("received urlChanged "+url.toString());
|
||||
log(".....................");
|
||||
_signals.push(std::make_pair("urlChanged",
|
||||
QStringList(url.toString())));
|
||||
}
|
||||
void timeout() {
|
||||
throw TimeOut();
|
||||
}
|
||||
private:
|
||||
typedef std::map<QString, std::shared_ptr<Command>> Prototypes;
|
||||
typedef std::vector<std::shared_ptr<Command>> Commands;
|
||||
Prototypes _prototypes;
|
||||
Commands _script;
|
||||
std::queue<Signal> _signals;
|
||||
QTimer _timer;
|
||||
QSet<QString> _ignores;
|
||||
QString _cout;
|
||||
QString _cerr;
|
||||
bool _screenshots;
|
||||
QString _ignoreSignalsUntil;
|
||||
QMap<QString, QString> _variables;
|
||||
int _timeout;
|
||||
};
|
||||
|
||||
class Do: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "do";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"do <selector>\n <javascript-line1>\n <javascript-line2>"
|
||||
"\n\n"
|
||||
"Execute JavaScript on a CSS selected object. The object is the first "
|
||||
"object in the DOM tree that matches the given CSS selector. You can "
|
||||
"refere to the selected object within the scripy by \"this\". The "
|
||||
"JavaScript code is on the following lines and at least intended by "
|
||||
"one space";
|
||||
}
|
||||
QString command() const {
|
||||
return "do "+_selector+_javascript;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args,
|
||||
QStringList& in, int) {
|
||||
std::shared_ptr<Do> cmd(new Do());
|
||||
cmd->_selector = args;
|
||||
while (in.size() && in[0].size() && in[0][0]==' ')
|
||||
cmd->_javascript += "\n"+in.takeFirst();
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
QWebElement element(find(frame, _selector, log));
|
||||
if (element.isNull()) throw ElementNotFound(_selector);
|
||||
_result =
|
||||
element.evaluateJavaScript(script->replacevars(_javascript)).toString();
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _selector;
|
||||
QString _javascript;
|
||||
};
|
||||
|
||||
class Load: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "load";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"load <url>"
|
||||
"\n\n"
|
||||
"Load an URL, the URL is given as parameter in full syntax.";
|
||||
}
|
||||
QString command() const {
|
||||
return "load "+_url;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Load> cmd(new Load());
|
||||
cmd->_url = args;
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
frame->load(script->replacevars(_url));
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _url;
|
||||
};
|
||||
|
||||
class Expect: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "expect";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"expect <signal> [<parameter>]"
|
||||
"\n\n"
|
||||
"Expect a signal. Signals are emitted by webkit and may contain "
|
||||
"parameter. If a parameter is given in the script, then the parameter "
|
||||
"must match exactly. If no parameter is given, then the signal must "
|
||||
"be emitted, but the parameters of the signal are not checked."
|
||||
"\n\n"
|
||||
"Known signals and parameters are:\n"
|
||||
" - loadFinished <bool: \"true\" if ok, \"false\" on error>\n"
|
||||
" - loadStarted\n"
|
||||
" - urlChanged <url>";
|
||||
}
|
||||
QString command() const {
|
||||
return "expect "+_signal+(_args.size()?" "+_args.join(' '):QString());
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Expect> cmd(new Expect());
|
||||
cmd->_args = args.split(" ");
|
||||
cmd->_signal = cmd->_args.takeFirst();
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
QString signal(_signal);
|
||||
QStringList args;
|
||||
Q_FOREACH(QString arg, _args) args.push_back(script->replacevars(arg));
|
||||
Script::Signal lastsignal(script->getSignal());
|
||||
QStringList lastargs;
|
||||
Q_FOREACH(QString arg, lastsignal.second)
|
||||
lastargs.push_back(script->replacevars(arg));
|
||||
if (lastsignal.first!=signal || (args.size() && args!=lastargs))
|
||||
throw WrongSignal(signal, args, lastsignal);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _signal;
|
||||
QStringList _args;
|
||||
};
|
||||
|
||||
class Open: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "open";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"open"
|
||||
"\n\n"
|
||||
"Open the browser window, so you can follow the test steps visually.";
|
||||
}
|
||||
QString command() const {
|
||||
return "open";
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
||||
std::shared_ptr<Open> cmd(new Open());
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
frame->page()->view()->show();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class Sleep: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "sleep";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"sleep <seconds>"
|
||||
"\n\n"
|
||||
"Sleep for a certain amount of seconds. This helps, if you must wait "
|
||||
"for some javascript actions, i.e. AJAX or slow pages, and the "
|
||||
"excpeted signals are not sufficient.";
|
||||
}
|
||||
QString command() const {
|
||||
return "sleep "+_time;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString time, QStringList&, int) {
|
||||
std::shared_ptr<Sleep> cmd(new Sleep());
|
||||
cmd->_time = "10"; // default: 10s
|
||||
if (time.size()) cmd->_time = time;
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
script->timer().stop();
|
||||
bool ok;
|
||||
unsigned int time(script->replacevars(_time).toUInt(&ok));
|
||||
if (!ok)
|
||||
throw BadArgument(script->replacevars(_time)
|
||||
+" should be a number of seconds");
|
||||
sleep(time);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _time;
|
||||
};
|
||||
|
||||
class Exit: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "exit";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"exit"
|
||||
"\n\n"
|
||||
"Successfully terminate script immediately. The following commands "
|
||||
"are not executed. This helps when you debug your scripts and you "
|
||||
"want the script stop at a certain point for investigations.";
|
||||
}
|
||||
QString command() const {
|
||||
return "exit";
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
||||
std::shared_ptr<Exit> cmd(new Exit());
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class IgnoreTo: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "ignoreto";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"ignoreto <label>"
|
||||
"\n\n"
|
||||
"Ignore all following commands up to a given label. The following "
|
||||
"commands are not executed until the given label appears in the "
|
||||
"script. This helps when you debug your scripts and you "
|
||||
"want to skip some lines of script code.";
|
||||
}
|
||||
QString command() const {
|
||||
return "ignoreto "+_label;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<IgnoreTo> cmd(new IgnoreTo());
|
||||
if (!args.size()) throw BadArgument("ignoreto needs a label");
|
||||
cmd->_label=args;
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
script->ignoreto(script->replacevars(_label));
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _label;
|
||||
};
|
||||
|
||||
class Label: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "label";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"label <label>"
|
||||
"\n\n"
|
||||
"This marks the label refered by command \"ignoreto\".";
|
||||
}
|
||||
QString command() const {
|
||||
return "label "+_label;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Label> cmd(new Label());
|
||||
if (!args.size()) throw BadArgument("label needs a label as parameter");
|
||||
cmd->_label=args;
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
script->label(script->replacevars(_label));
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _label;
|
||||
};
|
||||
|
||||
class Upload: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "upload";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"upload <selector> -> <filename>"
|
||||
"\n\n"
|
||||
"Presses the specified file upload button and passes a given file "
|
||||
"name. The command requires a CSS selector followed by a filename. "
|
||||
"The first object that matches the selector is used.";
|
||||
}
|
||||
QString command() const {
|
||||
return "upload "+_selector+" -> "+_filename;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Upload> cmd(new Upload());
|
||||
QStringList allargs(args.split("->"));
|
||||
if (allargs.size()<2)
|
||||
throw BadArgument("upload needs a selector folowed by a filename, "
|
||||
"instead of: \""+args+"\"");
|
||||
cmd->_selector = allargs.takeFirst().trimmed();
|
||||
cmd->_filename = allargs.join(" ").trimmed();
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
TestWebPage* page(dynamic_cast<TestWebPage*>(frame->page()));
|
||||
assert(page);
|
||||
QString filename(script->replacevars(_filename));
|
||||
if (!QFileInfo(filename).exists()) {
|
||||
QStringList files(QFileInfo(filename).dir()
|
||||
.entryList(QStringList(filename)));
|
||||
if (files.size()==1) filename=files[0];
|
||||
}
|
||||
page->setNextUploadFile(filename);
|
||||
realMouseClick(frame, script->replacevars(_selector), log);
|
||||
if (page->uploadPrepared())
|
||||
throw SetFileUploadFailed(script->replacevars(_selector), filename);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _selector;
|
||||
QString _filename;
|
||||
};
|
||||
|
||||
class Exists: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "exists";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"exists <selector> -> <text>"
|
||||
"\n\n"
|
||||
"Assert that a certain text exists in the selected object, or if no "
|
||||
"text is given, assert that the specified object exists. The object "
|
||||
"is given by a CSS selector. All matching objects are search for the "
|
||||
"text.";
|
||||
}
|
||||
QString command() const {
|
||||
return "exists "+_selector+(_text.size()?" -> "+_text:QString());
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Exists> cmd(new Exists());
|
||||
QStringList allargs(args.split("->"));
|
||||
if (allargs.size()<2) {
|
||||
cmd->_selector = args;
|
||||
} else {
|
||||
cmd->_selector = allargs.takeFirst().trimmed();
|
||||
cmd->_text = allargs.join(" ").trimmed();
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
QString selector(script->replacevars(_selector));
|
||||
QString text(script->replacevars(_text));
|
||||
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
|
||||
if (text.isEmpty()) return true; // just find element
|
||||
if (element.toOuterXml().indexOf(text)!=-1) return true;
|
||||
}
|
||||
QWebElement element(find(frame, selector, log));
|
||||
if (text.isEmpty())
|
||||
throw AssertionFailed("element not found: "+selector);
|
||||
else if (element.isNull())
|
||||
throw AssertionFailed("expected \""+text+"\" in non existing element "
|
||||
+selector);
|
||||
else
|
||||
throw AssertionFailed("not found \""+text+"\" in \""
|
||||
+element.toOuterXml()+"\" on "+selector);
|
||||
return true; // never reached due to throw above
|
||||
}
|
||||
private:
|
||||
QString _selector;
|
||||
QString _text;
|
||||
};
|
||||
|
||||
class Not: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "not";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"not <selector> -> <text>"
|
||||
"\n\n"
|
||||
"Assert that a certain text does not exists in the selected object, "
|
||||
"or if no text is given, assert that the specified object does not "
|
||||
"exists. The object is given by a CSS selector. All matching objects "
|
||||
"are search for the text.";
|
||||
}
|
||||
QString command() const {
|
||||
return "not "+_selector+(_text.size()?" -> "+_text:QString());
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Not> cmd(new Not());
|
||||
QStringList allargs(args.split("->"));
|
||||
if (allargs.size()<2) {
|
||||
cmd->_selector = args;
|
||||
} else {
|
||||
cmd->_selector = allargs.takeFirst().trimmed();
|
||||
cmd->_text = allargs.join(" ").trimmed();
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
QString selector(script->replacevars(_selector));
|
||||
QString text(script->replacevars(_text));
|
||||
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
|
||||
if (text.isEmpty())
|
||||
throw AssertionFailed("element must not exists: "+selector);
|
||||
if (element.toOuterXml().indexOf(text)!=-1)
|
||||
throw AssertionFailed("\""+text+"\" must not be in \""
|
||||
+element.toInnerXml()+"\" on "+selector);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _selector;
|
||||
QString _text;
|
||||
};
|
||||
|
||||
class Execute: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "execute";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"execute <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 "
|
||||
"one space are streamed into the command. This way, you can e.g. "
|
||||
"execute a bash script to check a file you downloaded from a page.";
|
||||
}
|
||||
QString command() const {
|
||||
QStringList script(_script);
|
||||
script.replaceInStrings(QRegExp("^"), " ");
|
||||
return "execute "+_command
|
||||
+(_args.size()?" "+_args.join(' '):QString())
|
||||
+(script.size()?"\n"+script.join("\n"):QString());
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args,
|
||||
QStringList& in, int) {
|
||||
std::shared_ptr<Execute> cmd(new Execute());
|
||||
cmd->_args = args.split(' ');
|
||||
cmd->_command = cmd->_args.takeFirst();
|
||||
int pos(-1);
|
||||
while (in.size() && in[0].size() && in[0][0]==' ') {
|
||||
if (pos<0) pos=in[0].toStdString().find_first_not_of(' ');
|
||||
cmd->_script += in.takeFirst().mid(pos);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
script->timer().stop();
|
||||
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));
|
||||
QProcess exec;
|
||||
exec.setProcessChannelMode(QProcess::MergedChannels);
|
||||
exec.start(command, args);
|
||||
if (!exec.waitForStarted())
|
||||
throw CannotStartScript(command, args, scripttxt);
|
||||
if (scripttxt.size()) {
|
||||
if (exec.write(scripttxt.toUtf8())!=scripttxt.toUtf8().size() ||
|
||||
!exec.waitForBytesWritten(60000))
|
||||
throw CannotLoadScript(command, args, scripttxt);
|
||||
}
|
||||
exec.closeWriteChannel();
|
||||
if (!exec.waitForFinished(60000) && exec.state()!=QProcess::NotRunning)
|
||||
throw ScriptNotFinished(command, args, scripttxt);
|
||||
QString stdout(exec.readAllStandardOutput());
|
||||
QString stderr(exec.readAllStandardError());
|
||||
_result = stdout;
|
||||
script->log(stdout);
|
||||
if (exec.exitCode()!=0 || exec.exitStatus()!=QProcess::NormalExit)
|
||||
throw ScriptExecutionFailed(command, args, scripttxt,
|
||||
exec.exitCode(), stdout, stderr);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _command;
|
||||
QStringList _args;
|
||||
QStringList _script;
|
||||
};
|
||||
|
||||
class Download: public Command {
|
||||
Q_OBJECT;
|
||||
public:
|
||||
QString tag() const {
|
||||
return "download";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"download <filename>"
|
||||
"<command-to-start-download>"
|
||||
"\n\n"
|
||||
"Set download file before loading a download link or clicking on a "
|
||||
"download button. The next line must be exactly one command that "
|
||||
"initiates the download.";
|
||||
}
|
||||
QString command() const {
|
||||
return "download"+(_filename.size()?" "+_filename:QString())+"\n"
|
||||
+_next->command();
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script* script, QString args,
|
||||
QStringList& in, int line) {
|
||||
std::shared_ptr<Download> cmd(new Download());
|
||||
cmd->_filename = args.trimmed();
|
||||
cmd->_next = script->parse(in, line+1);
|
||||
cmd->_next->log(false); // suppress logging of subcommand
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
frame->page()->setForwardUnsupportedContent(true);
|
||||
connect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||
this, SLOT(unsupportedContent(QNetworkReply*)));
|
||||
try {
|
||||
bool res(_next->execute(script, frame)); // start download
|
||||
script->timer().stop(); // no timeout during download
|
||||
for (_done = false; !_done;) // wait for download finish
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
log.log("download terminated "+
|
||||
QString(_netsuccess&&_filesuccess?"successfully":"with error"));
|
||||
if (!_netsuccess) throw DownloadFailed(_realfilename);
|
||||
if (!_filesuccess) throw WriteFileFailed(_realfilename);
|
||||
log.plainlog("[[ATTACHMENT|"+QDir(_realfilename).absolutePath()+"]]");
|
||||
disconnect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||
this, SLOT(unsupportedContent(QNetworkReply*)));
|
||||
return res;
|
||||
} catch (...) {
|
||||
disconnect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||
this, SLOT(unsupportedContent(QNetworkReply*)));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
private Q_SLOTS:
|
||||
void completed(bool netsuccess, bool filesuccess) {
|
||||
_done = true;
|
||||
_netsuccess = netsuccess;
|
||||
_filesuccess = filesuccess;
|
||||
}
|
||||
void unsupportedContent(QNetworkReply* reply) {
|
||||
_realfilename = reply->url().toString().split('/').last();
|
||||
if (_filename.size())
|
||||
_realfilename = _filename;
|
||||
else if (reply->header(QNetworkRequest::ContentDispositionHeader)
|
||||
.isValid()) {
|
||||
QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
|
||||
.toString());
|
||||
if (part.contains(QRegExp("attachment; *filename="))) {
|
||||
part.replace(QRegExp(".*attachment; *filename="), "");
|
||||
if (part.size()) _realfilename = part;
|
||||
}
|
||||
}
|
||||
connect(new RunDownload(reply, _realfilename),
|
||||
SIGNAL(completed(bool, bool)), SLOT(completed(bool, bool)));
|
||||
}
|
||||
private:
|
||||
QString _filename;
|
||||
QString _realfilename;
|
||||
std::shared_ptr<Command> _next;
|
||||
bool _done, _netsuccess, _filesuccess;
|
||||
};
|
||||
|
||||
class Click: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "click";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"click <selector>"
|
||||
"\n\n"
|
||||
"Click on the specified element";
|
||||
}
|
||||
QString command() const {
|
||||
return "click "+_selector;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||
std::shared_ptr<Click> cmd(new Click());
|
||||
cmd->_selector = args;
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
realMouseClick(frame, script->replacevars(_selector), log);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _selector;
|
||||
};
|
||||
|
||||
class Set: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "set";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"set <variable>=<value>\n"
|
||||
"set <variable>\n"
|
||||
" <command>"
|
||||
"\n\n"
|
||||
"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.";
|
||||
}
|
||||
QString command() const {
|
||||
if (_next)
|
||||
return "set "+_name+"\n "+_next->command();
|
||||
else
|
||||
return "set "+_name+" = "+_value;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script* script, QString args,
|
||||
QStringList& in, int line) {
|
||||
std::shared_ptr<Set> cmd(new Set());
|
||||
cmd->_next = 0;
|
||||
QStringList allargs(args.split("="));
|
||||
cmd->_name = allargs.takeFirst().trimmed();
|
||||
cmd->_value = allargs.join("=").trimmed();
|
||||
if (!args.contains('=')) {
|
||||
cmd->_next = script->parse(in, line+1);
|
||||
cmd->_next->log(false); // suppress logging of subcommand
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
if (_next) {
|
||||
_next->execute(script, frame);
|
||||
script->set(script->replacevars(_name),
|
||||
script->replacevars(_next->result()));
|
||||
} else {
|
||||
script->set(script->replacevars(_name),
|
||||
script->replacevars(_value));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _name;
|
||||
QString _value;
|
||||
std::shared_ptr<Command> _next;
|
||||
};
|
||||
|
||||
class UnSet: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "unset";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"unset <variable>"
|
||||
"\n\n"
|
||||
"Undo the setting of a variable. The opposite of «set».";
|
||||
}
|
||||
QString command() const {
|
||||
return "unset "+_name;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args,
|
||||
QStringList&, int) {
|
||||
std::shared_ptr<UnSet> cmd(new UnSet());
|
||||
cmd->_name = args;
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
script->unset(_name);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _name;
|
||||
};
|
||||
|
||||
class Timeout: public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "timeout";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
"timeout <seconds>"
|
||||
"\n\n"
|
||||
"Set the timeout in seconds (defaults to 10).";
|
||||
}
|
||||
QString command() const {
|
||||
return "timeout "+_timeout;
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args,
|
||||
QStringList&, int) {
|
||||
std::shared_ptr<Timeout> cmd(new Timeout());
|
||||
cmd->_timeout = args;
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame*) {
|
||||
Logger log(this, script);
|
||||
bool ok;
|
||||
int timeout(script->replacevars(_timeout).toInt(&ok));
|
||||
if (!ok) throw BadArgument(script->replacevars(_timeout)
|
||||
+" should be a number of seconds");
|
||||
script->timeout(timeout);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QString _timeout;
|
||||
};
|
||||
|
||||
|
||||
/* Template:
|
||||
class : public Command {
|
||||
public:
|
||||
QString tag() const {
|
||||
return "";
|
||||
}
|
||||
QString description() const {
|
||||
return
|
||||
""
|
||||
"\n\n"
|
||||
"";
|
||||
}
|
||||
QString command() const {
|
||||
return "";
|
||||
}
|
||||
std::shared_ptr<Command> parse(Script*, QString args,
|
||||
QStringList& in, int) {
|
||||
std::shared_ptr<> cmd(new ());
|
||||
return cmd;
|
||||
}
|
||||
bool execute(Script* script, QWebFrame* frame) {
|
||||
Logger log(this, script);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
inline bool Screenshot::execute(Script* script, QWebFrame* frame) {
|
||||
if (!script->screenshots()) return true;
|
||||
Logger log(this, script);
|
||||
QString filename(screenshot(line(), targetdir(),
|
||||
QFileInfo(testsuite()).baseName(),
|
||||
_filename, frame));
|
||||
log.plainlog("[[ATTACHMENT|"+filename+"]]");
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Logger::Logger(Command* command, Script* script):
|
||||
_command(command), _script(script) {
|
||||
if (_command->log()) {
|
||||
_script->log("---- line: "+QString::number(_command->line()));
|
||||
_script->log(_command->command());
|
||||
}
|
||||
}
|
||||
inline void Logger::log(QString txt) {
|
||||
if (_command->log())
|
||||
_script->log(txt);
|
||||
}
|
||||
inline void Logger::plainlog(QString txt) {
|
||||
_script->plainlog(txt);
|
||||
}
|
||||
inline Logger::~Logger() {
|
||||
if (_command->log())
|
||||
_script->log("---------------------");
|
||||
}
|
||||
|
||||
inline void Script::initPrototypes() {
|
||||
add(new Do);
|
||||
add(new Load);
|
||||
add(new Expect);
|
||||
add(new Screenshot);
|
||||
add(new Open);
|
||||
add(new Sleep);
|
||||
add(new Exit);
|
||||
add(new IgnoreTo);
|
||||
add(new Label);
|
||||
add(new Upload);
|
||||
add(new Exists);
|
||||
add(new Not);
|
||||
add(new Execute);
|
||||
add(new Download);
|
||||
add(new Click);
|
||||
add(new Set);
|
||||
add(new UnSet);
|
||||
add(new Timeout);
|
||||
}
|
||||
|
||||
#endif
|
202
src/exceptions.hxx
Normal file
202
src/exceptions.hxx
Normal file
@@ -0,0 +1,202 @@
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
#ifndef EXCEPTIONS_HXX
|
||||
#define EXCEPTIONS_HXX
|
||||
#include <stdexcept>
|
||||
#include <queue>
|
||||
#include <QDir>
|
||||
|
||||
class Exception: public std::exception {
|
||||
public:
|
||||
Exception(QString w): _what(w) {}
|
||||
~Exception() throw() {}
|
||||
const char* what() const throw() {
|
||||
return _what.toStdString().c_str();
|
||||
}
|
||||
void line(int linenr) {
|
||||
if (linenr>0) _what="line "+QString::number(linenr)+": "+_what;
|
||||
}
|
||||
protected:
|
||||
QString _what;
|
||||
};
|
||||
|
||||
class ParseError: public Exception {
|
||||
public:
|
||||
ParseError(QString w): Exception("parse error: "+w) {}
|
||||
};
|
||||
|
||||
class UnknownCommand: public ParseError {
|
||||
public:
|
||||
UnknownCommand(QString line): ParseError("unknown command: \""+line+"\"") {}
|
||||
};
|
||||
|
||||
class BadArgument: public ParseError {
|
||||
public:
|
||||
BadArgument(QString arg): ParseError("bad argument: "+arg) {}
|
||||
};
|
||||
|
||||
class TestFailed: public Exception {
|
||||
public:
|
||||
TestFailed(QString why): Exception("Test Failed: "+why) {}
|
||||
};
|
||||
|
||||
class PossibleRetryLoad: public TestFailed {
|
||||
public:
|
||||
PossibleRetryLoad(QString txt): TestFailed(txt) {}
|
||||
};
|
||||
|
||||
class WrongSignal: public PossibleRetryLoad {
|
||||
public:
|
||||
WrongSignal(QString signal, QStringList args,
|
||||
std::pair<QString, QStringList> received):
|
||||
PossibleRetryLoad
|
||||
("expected: \""+signal+" "+args.join(' ')+"\"; "
|
||||
"received: \""+received.first+" "+received.second.join(' ')+"\"") {
|
||||
}
|
||||
};
|
||||
|
||||
class UnhandledSignals: public TestFailed {
|
||||
public:
|
||||
typedef std::pair<QString, QStringList> Signal;
|
||||
UnhandledSignals(std::queue<Signal> sigs):
|
||||
TestFailed("unhandled signals:") {
|
||||
while (!sigs.empty()) {
|
||||
Signal res(sigs.front());
|
||||
_what += "\n"+res.first+" "+res.second.join(' ');
|
||||
sigs.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TimeOut: public PossibleRetryLoad {
|
||||
public:
|
||||
TimeOut(): PossibleRetryLoad("command timeout") {}
|
||||
};
|
||||
|
||||
class ElementNotFound: public TestFailed {
|
||||
public:
|
||||
ElementNotFound(QString selector):
|
||||
TestFailed("element not found: "+selector) {}
|
||||
};
|
||||
|
||||
class DirectoryCannotBeCreated: public TestFailed {
|
||||
public:
|
||||
DirectoryCannotBeCreated(QString name):
|
||||
TestFailed("cannot create directory: "+name) {}
|
||||
};
|
||||
|
||||
class CannotWriteScreenshot: public TestFailed {
|
||||
public:
|
||||
CannotWriteScreenshot(QString name):
|
||||
TestFailed("cannot write screenshot: "+name) {}
|
||||
};
|
||||
|
||||
class CannotWriteSouceHTML: public TestFailed {
|
||||
public:
|
||||
CannotWriteSouceHTML(QString name):
|
||||
TestFailed("cannot write html source code: "+name) {}
|
||||
};
|
||||
|
||||
class FileNotFound: public TestFailed {
|
||||
public:
|
||||
FileNotFound(QString arg): TestFailed("file not found: "+arg) {}
|
||||
};
|
||||
|
||||
class NotUnattended: public TestFailed {
|
||||
public:
|
||||
NotUnattended(): TestFailed("web page is not in unattended test mode") {}
|
||||
};
|
||||
|
||||
class LastFileNotUploaded: public TestFailed {
|
||||
public:
|
||||
LastFileNotUploaded(QString arg):
|
||||
TestFailed("last specified upload file has not been uploaded: "+arg) {
|
||||
}
|
||||
};
|
||||
|
||||
class EmptyUploadFile: public TestFailed {
|
||||
public:
|
||||
EmptyUploadFile(): TestFailed("specified upload file is empty string") {}
|
||||
};
|
||||
|
||||
class NoUploadFile: public TestFailed {
|
||||
public:
|
||||
NoUploadFile(): TestFailed("no upload file specified") {}
|
||||
};
|
||||
|
||||
class SetFileUploadFailed: public TestFailed {
|
||||
public:
|
||||
SetFileUploadFailed(QString selector, QString filename):
|
||||
TestFailed("set file upload failed for selector "+selector
|
||||
+" and file "+filename) {
|
||||
}
|
||||
};
|
||||
|
||||
class AssertionFailed: public TestFailed {
|
||||
public:
|
||||
AssertionFailed(QString text):
|
||||
TestFailed("assertion failed: "+text) {
|
||||
}
|
||||
};
|
||||
|
||||
class DownloadFailed: public TestFailed {
|
||||
public:
|
||||
DownloadFailed(QString file):
|
||||
TestFailed("download failed of file \""+file+"\"") {
|
||||
}
|
||||
};
|
||||
|
||||
class WriteFileFailed: public TestFailed {
|
||||
public:
|
||||
WriteFileFailed(QString file):
|
||||
TestFailed("write failed of file \""+QDir(file).absolutePath()+"\"") {
|
||||
}
|
||||
};
|
||||
|
||||
class ScriptFailed: public TestFailed {
|
||||
public:
|
||||
ScriptFailed(QString dsc, QString cmd, QStringList args, QString script):
|
||||
TestFailed(dsc+"; command: "+cmd
|
||||
+(args.size()?" "+args.join(' '):QString())
|
||||
+(script.size()?"\n"+script:QString())) {
|
||||
}
|
||||
};
|
||||
|
||||
class CannotStartScript: public ScriptFailed {
|
||||
public:
|
||||
CannotStartScript(QString command, QStringList args, QString script):
|
||||
ScriptFailed("command not found", command, args, script) {
|
||||
}
|
||||
};
|
||||
|
||||
class CannotLoadScript: public ScriptFailed {
|
||||
public:
|
||||
CannotLoadScript(QString command, QStringList args, QString script):
|
||||
ScriptFailed("cannot load script data", command, args, script) {
|
||||
}
|
||||
};
|
||||
|
||||
class ScriptNotFinished: public ScriptFailed {
|
||||
public:
|
||||
ScriptNotFinished(QString command, QStringList args, QString script):
|
||||
ScriptFailed("command hangs, timeout", command, args, script) {
|
||||
}
|
||||
};
|
||||
|
||||
class ScriptExecutionFailed: public ScriptFailed {
|
||||
public:
|
||||
ScriptExecutionFailed(QString command, QStringList args, QString script,
|
||||
int code, QString stdout, QString stderr):
|
||||
ScriptFailed("failed with exit code "+QString::number(code)
|
||||
+(stdout.size()?"; stdout=\""+stdout+"\"":"")
|
||||
+(stderr.size()?"; stderr=\""+stderr+"\"":""),
|
||||
command, args, script) {
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
212
src/networkaccessmanager.hxx
Normal file
212
src/networkaccessmanager.hxx
Normal file
@@ -0,0 +1,212 @@
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
#ifndef __NETWORKACCESS_MANAGER_HXX__
|
||||
#define __NETWORKACCESS_MANAGER_HXX__
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkConfiguration>
|
||||
#include <QNetworkProxy>
|
||||
|
||||
class NetworkAccessManager: public QNetworkAccessManager {
|
||||
Q_OBJECT;
|
||||
public:
|
||||
NetworkAccessManager(QObject* parent = 0): QNetworkAccessManager(parent) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
connect(this,
|
||||
SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
|
||||
SLOT(authenticationRequiredLog(QNetworkReply*, QAuthenticator*)));
|
||||
connect(this,
|
||||
SIGNAL(encrypted(QNetworkReply*)),
|
||||
SLOT(encryptedLog(QNetworkReply*)));
|
||||
connect(this,
|
||||
SIGNAL(finished(QNetworkReply*)),
|
||||
SLOT(finishedLog(QNetworkReply*)));
|
||||
connect(this,
|
||||
SIGNAL(networkAccessibleChanged
|
||||
(QNetworkAccessManager::NetworkAccessibility)),
|
||||
SLOT(networkAccessibleChangedLog
|
||||
(QNetworkAccessManager::NetworkAccessibility)));
|
||||
connect(this,
|
||||
SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&,
|
||||
QAuthenticator*)),
|
||||
SLOT(proxyAuthenticationRequiredLog(const QNetworkProxy&,
|
||||
QAuthenticator*)));
|
||||
connect(this,
|
||||
SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)),
|
||||
SLOT(sslErrorsLog(QNetworkReply*, const QList<QSslError>&)));
|
||||
}
|
||||
virtual ~NetworkAccessManager() {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
}
|
||||
QNetworkConfiguration activeConfiguration() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::activeConfiguration();
|
||||
}
|
||||
QAbstractNetworkCache* cache() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::cache();
|
||||
}
|
||||
void clearAccessCache() {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::clearAccessCache();
|
||||
}
|
||||
QNetworkConfiguration configuration() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::configuration();
|
||||
}
|
||||
void connectToHost(const QString& hostName, quint16 port = 80) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
QNetworkAccessManager::connectToHost(hostName, port);
|
||||
}
|
||||
void connectToHostEncrypted(const QString& hostName, quint16 port = 443,
|
||||
const QSslConfiguration& sslConfiguration
|
||||
= QSslConfiguration::defaultConfiguration()) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
QNetworkAccessManager::connectToHostEncrypted(hostName, port,
|
||||
sslConfiguration);
|
||||
}
|
||||
QNetworkCookieJar* cookieJar() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::cookieJar();
|
||||
}
|
||||
QNetworkReply* deleteResource(const QNetworkRequest & request) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::deleteResource(request);
|
||||
}
|
||||
QNetworkReply* get(const QNetworkRequest& request) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::get(request);
|
||||
}
|
||||
QNetworkReply* head(const QNetworkRequest& request) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::head(request);
|
||||
}
|
||||
NetworkAccessibility networkAccessible() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::networkAccessible();
|
||||
}
|
||||
QNetworkReply* post(const QNetworkRequest & request, QIODevice * data) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::post(request, data);
|
||||
}
|
||||
QNetworkReply* post(const QNetworkRequest& request,
|
||||
const QByteArray& data) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::post(request, data);
|
||||
}
|
||||
QNetworkReply* post(const QNetworkRequest& request,
|
||||
QHttpMultiPart* multiPart) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::post(request, multiPart);
|
||||
}
|
||||
QNetworkProxy proxy() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::proxy();
|
||||
}
|
||||
QNetworkProxyFactory* proxyFactory() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::proxyFactory();
|
||||
}
|
||||
QNetworkReply* put(const QNetworkRequest& request, QIODevice* data) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::put(request, data);
|
||||
}
|
||||
QNetworkReply* put(const QNetworkRequest& request,
|
||||
QHttpMultiPart* multiPart) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::put(request, multiPart);
|
||||
}
|
||||
QNetworkReply* put(const QNetworkRequest& request, const QByteArray& data) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::put(request, data);
|
||||
}
|
||||
QNetworkReply* sendCustomRequest(const QNetworkRequest& request,
|
||||
const QByteArray& verb,
|
||||
QIODevice* data = 0) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::sendCustomRequest(request, verb, data);
|
||||
}
|
||||
void setCache(QAbstractNetworkCache* cache) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::setCache(cache);
|
||||
}
|
||||
void setConfiguration(const QNetworkConfiguration& config) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::setConfiguration(config);
|
||||
}
|
||||
void setCookieJar(QNetworkCookieJar* cookieJar) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::setCookieJar(cookieJar);
|
||||
}
|
||||
void setNetworkAccessible(NetworkAccessibility accessible) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::setNetworkAccessible(accessible);
|
||||
}
|
||||
void setProxy(const QNetworkProxy& proxy) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::setProxy(proxy);
|
||||
}
|
||||
void setProxyFactory(QNetworkProxyFactory* factory) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::setProxyFactory(factory);
|
||||
}
|
||||
QStringList supportedSchemes() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::supportedSchemes();
|
||||
}
|
||||
Q_SIGNALS:
|
||||
void authenticationRequired(QNetworkReply*, QAuthenticator*);
|
||||
void encrypted(QNetworkReply*);
|
||||
void finished(QNetworkReply*);
|
||||
void networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility);
|
||||
void proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*);
|
||||
void sslErrors(QNetworkReply*, const QList<QSslError>&);
|
||||
void log(QString) const;
|
||||
protected:
|
||||
virtual QNetworkReply* createRequest(Operation op,
|
||||
const QNetworkRequest& req,
|
||||
QIODevice* outgoingData = 0) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
switch (op) {
|
||||
case QNetworkAccessManager::HeadOperation: break;
|
||||
case QNetworkAccessManager::GetOperation: break;
|
||||
case QNetworkAccessManager::PutOperation: break;
|
||||
case QNetworkAccessManager::PostOperation: break;
|
||||
case QNetworkAccessManager::DeleteOperation: break;
|
||||
case QNetworkAccessManager::CustomOperation: break;
|
||||
case QNetworkAccessManager::UnknownOperation: break;
|
||||
}
|
||||
return QNetworkAccessManager::createRequest(op, req, outgoingData);
|
||||
}
|
||||
protected Q_SLOTS:
|
||||
QStringList supportedSchemesImplementation() const {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
return QNetworkAccessManager::supportedSchemesImplementation();
|
||||
}
|
||||
private Q_SLOTS:
|
||||
void authenticationRequiredLog(QNetworkReply*, QAuthenticator*) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
}
|
||||
void encryptedLog(QNetworkReply*) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
}
|
||||
void finishedLog(QNetworkReply*) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
}
|
||||
void networkAccessibleChangedLog
|
||||
(QNetworkAccessManager::NetworkAccessibility) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
}
|
||||
void proxyAuthenticationRequiredLog(const QNetworkProxy&, QAuthenticator*) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
}
|
||||
void sslErrorsLog(QNetworkReply*, const QList<QSslError>&) {
|
||||
// log(__PRETTY_FUNCTION__);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
473
src/testgui.hxx
Normal file
473
src/testgui.hxx
Normal file
@@ -0,0 +1,473 @@
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
#ifndef TESTGUI_HXX
|
||||
#define TESTGUI_HXX
|
||||
|
||||
#include <webpage.hxx>
|
||||
#include <commands.hxx>
|
||||
#include <QMainWindow>
|
||||
#include <QSettings>
|
||||
#include <QWebFrame>
|
||||
#include <QWebElement>
|
||||
#include <QFileDialog>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <ui_testgui.h>
|
||||
#include <stdexcept>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
||||
Q_OBJECT;
|
||||
public:
|
||||
explicit TestGUI(QWidget *parent = 0, QString url = QString()):
|
||||
QMainWindow(parent),
|
||||
_typing(false),
|
||||
_inEventFilter(false) {
|
||||
setupUi(this);
|
||||
QSettings settings("mrw", "webtester");
|
||||
restoreGeometry(settings.value("geometry").toByteArray());
|
||||
restoreState(settings.value("windowstate").toByteArray());
|
||||
if (!url.isEmpty()) {
|
||||
_url->setText(url);
|
||||
}
|
||||
TestWebPage* page(new TestWebPage(_web));
|
||||
_web->setPage(page);
|
||||
_web->installEventFilter(this); // track mouse and keyboard
|
||||
page->setForwardUnsupportedContent(true);
|
||||
connect(page, SIGNAL(uploadFile(QString)), SLOT(uploadFile(QString)));
|
||||
connect(page, SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||
SLOT(unsupportedContent(QNetworkReply*)));
|
||||
connect(page, SIGNAL(downloadRequested(const QNetworkRequest&)),
|
||||
SLOT(downloadRequested(const QNetworkRequest&)));
|
||||
}
|
||||
virtual ~TestGUI() {}
|
||||
public Q_SLOTS:
|
||||
void on__load_clicked() {
|
||||
enterText(true);
|
||||
if (_record->isChecked())
|
||||
_testscript->appendPlainText("load "+_url->text());
|
||||
_web->load(_url->text());
|
||||
}
|
||||
void on__abort_clicked() {
|
||||
enterText(true);
|
||||
_web->stop();
|
||||
}
|
||||
void on__actionOpen_triggered() {
|
||||
QString name(QFileDialog::getOpenFileName(this, tr("Open Test Script")));
|
||||
if (name.isEmpty()) return;
|
||||
on__actionRevertToSaved_triggered(name);
|
||||
}
|
||||
void on__actionRevertToSaved_triggered() {
|
||||
on__actionRevertToSaved_triggered(_filename);
|
||||
}
|
||||
void on__actionRevertToSaved_triggered(QString name) {
|
||||
QFile file(name);
|
||||
try {
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
throw std::runtime_error("file open failed");
|
||||
_testscript->setPlainText(QString::fromUtf8(file.readAll()));
|
||||
if (file.error()!=QFileDevice::NoError)
|
||||
throw std::runtime_error("file read failed");
|
||||
_filename = name;
|
||||
_actionSave->setEnabled(true);
|
||||
_actionRevertToSaved->setEnabled(true);
|
||||
} catch(const std::exception& x) {
|
||||
QMessageBox::critical(this, tr("Open Failed"),
|
||||
tr("Reading test script failed, %2. "
|
||||
"Cannot read test script from file %1.")
|
||||
.arg(name).arg(x.what()));
|
||||
}
|
||||
}
|
||||
void on__actionSaveAs_triggered() {
|
||||
QString name(QFileDialog::getSaveFileName(this, tr("Save Test Script")));
|
||||
if (name.isEmpty()) return;
|
||||
_filename = name;
|
||||
on__actionSave_triggered();
|
||||
}
|
||||
void on__actionSave_triggered() {
|
||||
QFile file(_filename);
|
||||
try {
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
throw std::runtime_error("file open failed");
|
||||
QTextStream out(&file);
|
||||
out<<_testscript->toPlainText();
|
||||
if (out.status()!=QTextStream::Ok)
|
||||
throw std::runtime_error(std::string("file write failed (")
|
||||
+char(out.status()+48)+")");
|
||||
_actionSave->setEnabled(true);
|
||||
_actionRevertToSaved->setEnabled(true);
|
||||
} catch(const std::exception& x) {
|
||||
QMessageBox::critical(this, tr("Save Failed"),
|
||||
tr("Saving test script failed, %2. "
|
||||
"Cannot write test script to file %1.")
|
||||
.arg(_filename).arg(x.what()));
|
||||
}
|
||||
}
|
||||
void on__actionClear_triggered() {
|
||||
_testscript->clear();
|
||||
_filename.clear();
|
||||
_actionSave->setEnabled(false);
|
||||
_actionRevertToSaved->setEnabled(false);
|
||||
}
|
||||
void on__run_clicked() {
|
||||
bool oldRecordState(_record->isChecked());
|
||||
_run->setEnabled(false);
|
||||
try {
|
||||
xml::Node testsuites("testsuites");
|
||||
xml::Node testsuite("testsuite");
|
||||
testsuite.attr("name") = "on-the-fly";
|
||||
testsuite.attr("timestamp") =
|
||||
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
|
||||
xml::Node testcase("testcase");
|
||||
testcase.attr("classname") = "testsuite-preparation";
|
||||
QString text(_testscript->textCursor().selectedText());
|
||||
if (text.isEmpty()) text = _testscript->toPlainText();
|
||||
Script script;
|
||||
connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString)));
|
||||
script.parse(text.split('\n'));
|
||||
script.run(_web->page()->mainFrame(), testsuite, QString(), false);
|
||||
} catch (std::exception &x) {
|
||||
QMessageBox::critical(this, tr("Script Failed"),
|
||||
tr("Script failed with message:\n%1")
|
||||
.arg(x.what()));
|
||||
}
|
||||
_run->setEnabled(true);
|
||||
_record->setChecked(oldRecordState);
|
||||
}
|
||||
void on__focused_clicked() {
|
||||
enterText(true);
|
||||
QWebElement element(focused());
|
||||
if (element.isNull()) return;
|
||||
highlight(element);
|
||||
_focusedText->setText(selector(element));
|
||||
}
|
||||
void on__select_clicked() {
|
||||
enterText(true);
|
||||
highlight(_web->page()->mainFrame()->documentElement()
|
||||
.findFirst(_selector->text()));
|
||||
}
|
||||
void on__jsClick_clicked() {
|
||||
enterText(true);
|
||||
execute(selector(),
|
||||
"this.click();");
|
||||
// "var evObj = document.createEvent('MouseEvents');\n"
|
||||
// "evObj.initEvent( 'click', true, true );\n"
|
||||
// "this.dispatchEvent(evObj);");
|
||||
}
|
||||
void on__jsValue_clicked() {
|
||||
enterText(true);
|
||||
QWebElement element(selected());
|
||||
execute(selector(element),
|
||||
"this.value='"+value(element).replace("\n", "\\n")+"';");
|
||||
}
|
||||
void on__jsExecute_clicked() {
|
||||
enterText(true);
|
||||
execute(selector(), _javascriptCode->toPlainText());
|
||||
}
|
||||
void on__web_linkClicked(const QUrl& url) {
|
||||
enterText(true);
|
||||
if (_record->isChecked())
|
||||
_testscript->appendPlainText("load "+url.url());
|
||||
}
|
||||
void on__web_loadProgress(int progress) {
|
||||
enterText(true);
|
||||
_progress->setValue(progress);
|
||||
}
|
||||
void on__web_loadStarted() {
|
||||
enterText(true);
|
||||
if (_record->isChecked())
|
||||
_testscript->appendPlainText("expect loadStarted");
|
||||
_progress->setValue(0);
|
||||
_urlStack->setCurrentIndex(PROGRESS_VIEW);
|
||||
}
|
||||
void on__web_statusBarMessage(const QString&) {
|
||||
//std::cout<<"statusBarMessage: "<<text.toStdString()<<std::endl;
|
||||
}
|
||||
void on__web_titleChanged(const QString&) {
|
||||
//std::cout<<"titleChanged: "<<title.toStdString()<<std::endl;
|
||||
}
|
||||
void on__web_urlChanged(const QUrl& url) {
|
||||
enterText(true);
|
||||
if (_record->isChecked())
|
||||
_testscript->appendPlainText("expect urlChanged "+url.url());
|
||||
}
|
||||
void on__web_selectionChanged() {
|
||||
_source->setPlainText(_web->hasSelection()
|
||||
? _web->selectedHtml()
|
||||
: _web->page()->mainFrame()->toHtml());
|
||||
}
|
||||
void on__web_loadFinished(bool ok) {
|
||||
enterText(true);
|
||||
if (_record->isChecked())
|
||||
_testscript->appendPlainText("expect loadFinished "
|
||||
+QString(ok?"true":"false"));
|
||||
_urlStack->setCurrentIndex(URL_VIEW);
|
||||
on__web_selectionChanged();
|
||||
setLinks();
|
||||
setForms();
|
||||
setDom();
|
||||
}
|
||||
void on__forms_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
|
||||
if (!item) return;
|
||||
_source->setPlainText(item->data(0, Qt::UserRole).toString());
|
||||
}
|
||||
void on__dom_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
|
||||
if (!item) return;
|
||||
_source->setPlainText(item->data(0, Qt::UserRole).toString());
|
||||
}
|
||||
void uploadFile(QString filename) {
|
||||
enterText(true);
|
||||
if (_record->isChecked())
|
||||
_testscript->appendPlainText("upload "+filename);
|
||||
}
|
||||
void unsupportedContent(QNetworkReply* reply) {
|
||||
if (!_record->isChecked()) return;
|
||||
QString filename(reply->url().toString().split('/').last());
|
||||
if (reply->header(QNetworkRequest::ContentDispositionHeader).isValid()) {
|
||||
QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
|
||||
.toString());
|
||||
if (part.contains(QRegExp("attachment; *filename="))) {
|
||||
part.replace(QRegExp(".*attachment; *filename="), "");
|
||||
if (part.size()) filename = part;
|
||||
}
|
||||
}
|
||||
QString text(_testscript->toPlainText());
|
||||
int pos1(text.lastIndexOf(QRegExp("^do ")));
|
||||
int pos2(text.lastIndexOf(QRegExp("^load ")));
|
||||
text.insert(pos1>pos2?pos1:pos2, "download "+filename);
|
||||
_testscript->setPlainText(text);
|
||||
_testscript->moveCursor(QTextCursor::End);
|
||||
_testscript->ensureCursorVisible();
|
||||
}
|
||||
void downloadRequested(const QNetworkRequest&) {
|
||||
if (_record->isChecked())
|
||||
_testscript->appendPlainText("download2");
|
||||
}
|
||||
void logging(QString txt) {
|
||||
_log->appendPlainText(txt);
|
||||
}
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) {
|
||||
QSettings settings("mrw", "webtester");
|
||||
settings.setValue("geometry", saveGeometry());
|
||||
settings.setValue("windowstate", saveState());
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
bool eventFilter(QObject*, QEvent* event) {
|
||||
if (_inEventFilter) return false;
|
||||
_inEventFilter = true;
|
||||
enterText();
|
||||
QWebElement element(focused(dynamic_cast<QMouseEvent*>(event)));
|
||||
switch (event->type()) {
|
||||
case QEvent::KeyPress: {
|
||||
QKeyEvent* k(dynamic_cast<QKeyEvent*>(event));
|
||||
switch (k->key()) {
|
||||
case Qt::Key_Tab:
|
||||
case Qt::Key_Backtab: {
|
||||
enterText(true);
|
||||
} break;
|
||||
case Qt::Key_Backspace: {
|
||||
_keyStrokes.chop(1);
|
||||
} break;
|
||||
case Qt::Key_Shift: break;
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return: {
|
||||
_keyStrokes += "\\n";
|
||||
_lastFocused=element;
|
||||
_typing = true;
|
||||
} break;
|
||||
default: {
|
||||
_keyStrokes += k->text();
|
||||
_lastFocused=element;
|
||||
_typing = true;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case QEvent::MouseButtonRelease: {
|
||||
enterText(true);
|
||||
_lastFocused=element;
|
||||
if (_record->isChecked() && !element.isNull())
|
||||
_testscript->appendPlainText("click "+selector(_lastFocused));
|
||||
} break;
|
||||
case QEvent::InputMethodQuery:
|
||||
case QEvent::ToolTipChange:
|
||||
case QEvent::MouseMove:
|
||||
case QEvent::UpdateLater:
|
||||
case QEvent::Paint: break;
|
||||
default: ;//LOG("Event: "<<event->type());
|
||||
}
|
||||
_inEventFilter = false;
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
void enterText(bool force=false) {
|
||||
if (!force && (!_typing || _lastFocused==focused())) return;
|
||||
if (_keyStrokes.size() && !_lastFocused.isNull()) {
|
||||
store(selector(_lastFocused), "this.value='"
|
||||
+value(_lastFocused).replace("\n", "\\n")+"';");
|
||||
}
|
||||
_lastFocused = QWebElement();
|
||||
_keyStrokes.clear();
|
||||
_typing = false;
|
||||
}
|
||||
QWebElement selected() {
|
||||
return _web->page()->mainFrame()->documentElement().findFirst(selector());
|
||||
}
|
||||
QString selector() {
|
||||
if (_takeFocused->isChecked())
|
||||
return selector(focused());
|
||||
else if (_takeSelect->isChecked())
|
||||
return _selector->text();
|
||||
else
|
||||
return QString(); // error
|
||||
}
|
||||
void highlight(QWebElement element) {
|
||||
element
|
||||
.evaluateJavaScript("var selection = window.getSelection();"
|
||||
"selection.setBaseAndExtent(this, 0, this, 1);");
|
||||
}
|
||||
QWebElement focused(QMouseEvent* event = 0) {
|
||||
Q_FOREACH(QWebElement element,
|
||||
_web->page()->currentFrame()->findAllElements("*")) {
|
||||
if (element.hasFocus()) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
if (event) { // try to find element using mouse position
|
||||
QWebFrame* frame(_web->page()->frameAt(event->pos()));
|
||||
if (frame) return frame->hitTestContent(event->pos()).element();
|
||||
}
|
||||
return QWebElement();
|
||||
}
|
||||
bool unique(QString selector) {
|
||||
return _web->page()->mainFrame()->findAllElements(selector).count()==1;
|
||||
}
|
||||
QString quote(QString txt) {
|
||||
if (txt.contains('"')) return "'"+txt+"'";
|
||||
return '"'+txt+'"';
|
||||
}
|
||||
QString selector(const QWebElement& element) {
|
||||
if (element.isNull()) return QString();
|
||||
if (element.hasAttribute("id") && unique("#"+element.attribute("id"))) {
|
||||
return "#"+element.attribute("id");
|
||||
} else if (element.hasAttribute("name") &&
|
||||
unique(element.tagName().toLower()
|
||||
+"[name="+quote(element.attribute("name"))+"]")) {
|
||||
return element.tagName().toLower()
|
||||
+"[name="+quote(element.attribute("name"))+"]";
|
||||
} else {
|
||||
QString res;
|
||||
Q_FOREACH(QString attr, element.attributeNames()) {
|
||||
if (attr=="id")
|
||||
res = "#"+element.attribute("id")+res;
|
||||
else if (attr=="class")
|
||||
Q_FOREACH(QString c, element.attribute(attr).split(' ')) {
|
||||
if (!c.isEmpty()) res = '.'+c+res;
|
||||
}
|
||||
else if (element.attribute(attr).isEmpty())
|
||||
res+="["+attr+"]";
|
||||
else
|
||||
res+="["+attr+"="+quote(element.attribute(attr))+"]";
|
||||
if (unique(element.tagName().toLower()+res))
|
||||
return element.tagName().toLower()+res;
|
||||
}
|
||||
QString p(selector(element.parent()));
|
||||
if (unique(p+">"+element.tagName().toLower()+res))
|
||||
return p+">"+element.tagName().toLower()+res;
|
||||
QString s(selector(element.previousSibling()));
|
||||
if (unique(s+"+"+element.tagName().toLower()+res))
|
||||
return s+"+"+element.tagName().toLower()+res;
|
||||
if (!p.isEmpty())
|
||||
return p+">"+element.tagName().toLower()+res;
|
||||
if (!s.isEmpty())
|
||||
return s+"+"+element.tagName().toLower()+res;
|
||||
return element.tagName().toLower()+res;
|
||||
}
|
||||
}
|
||||
QString value(QWebElement element) {
|
||||
return element.evaluateJavaScript("this.value").toString();
|
||||
//! @bug Bug in Qt, attribute("value") is always empty
|
||||
// if (element.hasAttribute("value"))
|
||||
// return element.attribute("value");
|
||||
// else
|
||||
// return element.toPlainText();
|
||||
}
|
||||
void store(const QString& selector, QString code) {
|
||||
if (_record->isChecked())
|
||||
_testscript->appendPlainText("do "+selector+"\n "
|
||||
+code.replace("\n", "\\n"));
|
||||
}
|
||||
void execute(const QString& selector, const QString& code) {
|
||||
store(selector, code);
|
||||
_web->page()->mainFrame()->documentElement().findFirst(selector)
|
||||
.evaluateJavaScript(code);
|
||||
}
|
||||
void setLinks() {
|
||||
QWebElementCollection links(_web->page()->mainFrame()->documentElement()
|
||||
.findAll("a"));
|
||||
_links->setRowCount(links.count());
|
||||
for (int row(0); row<_links->rowCount(); ++row) {
|
||||
{
|
||||
QTableWidgetItem* item(new QTableWidgetItem());
|
||||
item->setText(links[row].attribute("href"));
|
||||
_links->setItem(row, 0, item);
|
||||
} {
|
||||
QTableWidgetItem* item(new QTableWidgetItem());
|
||||
item->setText(links[row].hasAttribute("title")
|
||||
? links[row].attribute("title")
|
||||
: links[row].toInnerXml());
|
||||
_links->setItem(row, 1, item);
|
||||
}
|
||||
_links->horizontalHeader()->resizeSections(QHeaderView::Stretch);
|
||||
}
|
||||
|
||||
}
|
||||
void setForms() {
|
||||
QWebElementCollection forms(_web->page()->mainFrame()->documentElement()
|
||||
.findAll("form"));
|
||||
_forms->clear();
|
||||
Q_FOREACH(const QWebElement &form, forms) {
|
||||
addDomElement(form, _forms->invisibleRootItem());
|
||||
}
|
||||
|
||||
}
|
||||
void setDom() {
|
||||
_dom->clear();
|
||||
addDomElement(_web->page()->mainFrame()->documentElement(),
|
||||
_dom->invisibleRootItem());
|
||||
}
|
||||
//void addDomChildren(const QWebElement&, QTreeWidgetItem*);
|
||||
void addDomElement(const QWebElement &element,
|
||||
QTreeWidgetItem *parent) {
|
||||
QTreeWidgetItem *item(new QTreeWidgetItem());
|
||||
item->setText(0, element.tagName());
|
||||
item->setData(0, Qt::UserRole, element.toOuterXml());
|
||||
parent->addChild(item);
|
||||
addDomChildren(element, item);
|
||||
}
|
||||
void addDomChildren(const QWebElement &parentElement,
|
||||
QTreeWidgetItem *parentItem) {
|
||||
for (QWebElement element = parentElement.firstChild();
|
||||
!element.isNull();
|
||||
element = element.nextSibling()) {
|
||||
addDomElement(element, parentItem);
|
||||
}
|
||||
}
|
||||
private:
|
||||
enum UrlStack {
|
||||
URL_VIEW = 0,
|
||||
PROGRESS_VIEW
|
||||
};
|
||||
private:
|
||||
QString _filename;
|
||||
QWebElement _lastFocused; // cache for last focussed element
|
||||
QString _keyStrokes; // collect key strokes
|
||||
bool _typing; // user is typing
|
||||
bool _inEventFilter; // actually handling event filter
|
||||
};
|
||||
|
||||
#endif // TESTGUI_HXX
|
874
src/testgui.ui
Normal file
874
src/testgui.ui
Normal file
@@ -0,0 +1,874 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TestGUI</class>
|
||||
<widget class="QMainWindow" name="TestGUI">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>888</width>
|
||||
<height>1180</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="_urlStack">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>URL:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>_url</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="_url">
|
||||
<property name="text">
|
||||
<string>http://web9t.int.swisssign.net/joomla</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="_load">
|
||||
<property name="text">
|
||||
<string>load</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QProgressBar" name="_progress">
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="_abort">
|
||||
<property name="text">
|
||||
<string>Abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWebView" name="_web" native="true">
|
||||
<property name="url" stdset="0">
|
||||
<url>
|
||||
<string>about:blank</string>
|
||||
</url>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>888</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuViews">
|
||||
<property name="title">
|
||||
<string>Views</string>
|
||||
</property>
|
||||
<addaction name="_actionTestScript"/>
|
||||
<addaction name="_actionDOMTree"/>
|
||||
<addaction name="_actionLinks"/>
|
||||
<addaction name="_actionForms"/>
|
||||
<addaction name="_actionHTMLSouce"/>
|
||||
<addaction name="_actionExecuteJavaScript"/>
|
||||
<addaction name="_actionLog"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="_actionOpen"/>
|
||||
<addaction name="_actionSave"/>
|
||||
<addaction name="_actionSaveAs"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="_actionRun"/>
|
||||
<addaction name="_actionRunLine"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="_actionRevertToSaved"/>
|
||||
<addaction name="_actionClear"/>
|
||||
<addaction name="_actionQuit"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuViews"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<widget class="QDockWidget" name="_domDock">
|
||||
<property name="windowTitle">
|
||||
<string>DOM Tree</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_5">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeWidget" name="_dom">
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Element</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="_sourceDock">
|
||||
<property name="windowTitle">
|
||||
<string>HTML Source</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>8</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_7">
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPlainTextEdit" name="_source"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="_linksDock">
|
||||
<property name="windowTitle">
|
||||
<string>Links</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_8">
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTableWidget" name="_links">
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="_formsDock">
|
||||
<property name="windowTitle">
|
||||
<string>Forms</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_9">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeWidget" name="_forms">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="_executeDock">
|
||||
<property name="windowTitle">
|
||||
<string>Execute JavaScript on First Selected Item</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>8</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_10">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="_jsClick">
|
||||
<property name="text">
|
||||
<string>Click</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="_jsValue">
|
||||
<property name="text">
|
||||
<string>Set Value</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="_javascriptCode">
|
||||
<property name="toolTip">
|
||||
<string>JavaScript Code to Execute</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>JavaScript Code to Execute</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>JavaScript code to execute on the selected element. You can use this to refere to the element.</p><p>Example, to click the element:</p><pre>
|
||||
var evObj = document.createEvent('MouseEvents');
|
||||
evObj.initEvent( 'click', true, true );
|
||||
this.dispatchEvent(evObj);
|
||||
</pre></body></html></string>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string>var evObj = document.createEvent('MouseEvents');
|
||||
evObj.initEvent( 'click', true, true );
|
||||
this.dispatchEvent(evObj);</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="_jsExecute">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Execute</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="_takeFocused">
|
||||
<property name="text">
|
||||
<string>select focused</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="_takeSelect">
|
||||
<property name="text">
|
||||
<string>select:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="_focusedText">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="_selector">
|
||||
<property name="toolTip">
|
||||
<string>CSS-Selector</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Enter CSS-Selector</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><html><head/><body><p>JavaScript code below will be applied to the first element found by this CSS selector.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="_focused">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Focused</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="_select">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Test Selector</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Hilight Selection in Page</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Shows you the selection by hilighting it in the web page. This helps you to test before you execute.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder></zorder>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="_scriptDock">
|
||||
<property name="windowTitle">
|
||||
<string>Test Script</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>4</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_12">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" rowspan="3">
|
||||
<widget class="QPlainTextEdit" name="_testscript"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="_record">
|
||||
<property name="text">
|
||||
<string>Record</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="_run">
|
||||
<property name="text">
|
||||
<string>Run</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>82</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="_logDock">
|
||||
<property name="windowTitle">
|
||||
<string>Script Run Log</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>8</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_2">
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QPlainTextEdit" name="_log"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<action name="_actionDOMTree">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>DOM Tree</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionLinks">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Links</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionForms">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Forms</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionHTMLSouce">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>HTML Souce</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionExecuteJavaScript">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Execute JavaScript</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionTestScript">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Test Script</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionOpen">
|
||||
<property name="text">
|
||||
<string>Open ...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionSaveAs">
|
||||
<property name="text">
|
||||
<string>Save As ...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionQuit">
|
||||
<property name="text">
|
||||
<string>Quit</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionRun">
|
||||
<property name="text">
|
||||
<string>Run</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionRunLine">
|
||||
<property name="text">
|
||||
<string>Run Line</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionClear">
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionLog">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Log</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionSave">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="_actionRevertToSaved">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Revert to saved</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>QtWebKitWidgets/QWebView</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>_url</tabstop>
|
||||
<tabstop>_load</tabstop>
|
||||
<tabstop>_web</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>_url</sender>
|
||||
<signal>returnPressed()</signal>
|
||||
<receiver>_load</receiver>
|
||||
<slot>click()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>493</x>
|
||||
<y>343</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>580</x>
|
||||
<y>344</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_selector</sender>
|
||||
<signal>returnPressed()</signal>
|
||||
<receiver>_jsExecute</receiver>
|
||||
<slot>click()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>785</x>
|
||||
<y>1144</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>875</x>
|
||||
<y>1075</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_selector</sender>
|
||||
<signal>editingFinished()</signal>
|
||||
<receiver>_select</receiver>
|
||||
<slot>click()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>785</x>
|
||||
<y>1144</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>874</x>
|
||||
<y>1144</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_actionExecuteJavaScript</sender>
|
||||
<signal>triggered(bool)</signal>
|
||||
<receiver>_executeDock</receiver>
|
||||
<slot>setVisible(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>630</x>
|
||||
<y>971</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_executeDock</sender>
|
||||
<signal>visibilityChanged(bool)</signal>
|
||||
<receiver>_actionTestScript</receiver>
|
||||
<slot>setChecked(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>630</x>
|
||||
<y>971</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_actionDOMTree</sender>
|
||||
<signal>triggered(bool)</signal>
|
||||
<receiver>_domDock</receiver>
|
||||
<slot>setVisible(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>748</x>
|
||||
<y>374</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_domDock</sender>
|
||||
<signal>visibilityChanged(bool)</signal>
|
||||
<receiver>_actionDOMTree</receiver>
|
||||
<slot>setChecked(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>748</x>
|
||||
<y>374</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_actionLinks</sender>
|
||||
<signal>triggered(bool)</signal>
|
||||
<receiver>_linksDock</receiver>
|
||||
<slot>setVisible(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>748</x>
|
||||
<y>537</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_linksDock</sender>
|
||||
<signal>visibilityChanged(bool)</signal>
|
||||
<receiver>_actionLinks</receiver>
|
||||
<slot>setChecked(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>748</x>
|
||||
<y>537</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_actionForms</sender>
|
||||
<signal>triggered(bool)</signal>
|
||||
<receiver>_formsDock</receiver>
|
||||
<slot>setVisible(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>748</x>
|
||||
<y>699</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_formsDock</sender>
|
||||
<signal>visibilityChanged(bool)</signal>
|
||||
<receiver>_actionForms</receiver>
|
||||
<slot>setChecked(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>748</x>
|
||||
<y>699</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_actionHTMLSouce</sender>
|
||||
<signal>triggered(bool)</signal>
|
||||
<receiver>_sourceDock</receiver>
|
||||
<slot>setVisible(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>182</x>
|
||||
<y>971</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_sourceDock</sender>
|
||||
<signal>visibilityChanged(bool)</signal>
|
||||
<receiver>_actionHTMLSouce</receiver>
|
||||
<slot>setChecked(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>182</x>
|
||||
<y>971</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_actionTestScript</sender>
|
||||
<signal>triggered(bool)</signal>
|
||||
<receiver>_scriptDock</receiver>
|
||||
<slot>setVisible(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>443</x>
|
||||
<y>155</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_scriptDock</sender>
|
||||
<signal>visibilityChanged(bool)</signal>
|
||||
<receiver>_actionTestScript</receiver>
|
||||
<slot>setChecked(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>443</x>
|
||||
<y>155</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_actionLog</sender>
|
||||
<signal>triggered(bool)</signal>
|
||||
<receiver>_logDock</receiver>
|
||||
<slot>setVisible(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>842</x>
|
||||
<y>1004</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>_logDock</sender>
|
||||
<signal>visibilityChanged(bool)</signal>
|
||||
<receiver>_actionLog</receiver>
|
||||
<slot>setChecked(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>842</x>
|
||||
<y>1004</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
37
src/web.ui
Normal file
37
src/web.ui
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QWebView" name="_web">
|
||||
<property name="url">
|
||||
<url>
|
||||
<string>about:blank</string>
|
||||
</url>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>QtWebKitWidgets/QWebView</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
70
src/webpage.hxx
Normal file
70
src/webpage.hxx
Normal file
@@ -0,0 +1,70 @@
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
#ifndef WEBPAGE_HXX
|
||||
#define WEBPAGE_HXX
|
||||
|
||||
#include <exceptions.hxx>
|
||||
#include <networkaccessmanager.hxx>
|
||||
#include <QWebPage>
|
||||
#include <QWebFrame>
|
||||
#include <QWidget>
|
||||
#include <QFileDialog>
|
||||
#include <QFile>
|
||||
|
||||
#include <cassert>
|
||||
#ifndef LOG
|
||||
#include <iostream>
|
||||
#define LOG(X) std::clog<<X<<std::endl;
|
||||
inline std::ostream& operator<<(std::ostream& o, const QString& s) {
|
||||
return o<<s.toStdString();
|
||||
}
|
||||
#endif
|
||||
|
||||
class TestWebPage: public QWebPage {
|
||||
Q_OBJECT;
|
||||
public:
|
||||
TestWebPage(QObject* parent = 0, bool unattended = false):
|
||||
QWebPage(parent),
|
||||
_unattended(unattended) {
|
||||
setNetworkAccessManager(new NetworkAccessManager(this));
|
||||
}
|
||||
virtual ~TestWebPage() {
|
||||
if (!_nextFile.isEmpty() && !std::uncaught_exception())
|
||||
throw LastFileNotUploaded(_nextFile);
|
||||
}
|
||||
void setNextUploadFile(QString nextFile) {
|
||||
if (!_unattended) throw NotUnattended();
|
||||
if (!_nextFile.isEmpty()) throw LastFileNotUploaded(_nextFile);
|
||||
if (nextFile.isEmpty()) throw EmptyUploadFile();
|
||||
_nextFile = nextFile;
|
||||
}
|
||||
bool uploadPrepared() {
|
||||
return !_nextFile.isEmpty();
|
||||
}
|
||||
Q_SIGNALS:
|
||||
void uploadFile(QString filename);
|
||||
protected:
|
||||
virtual QString chooseFile(QWebFrame* frame, const QString&) {
|
||||
if (_unattended) {
|
||||
if (_nextFile.isEmpty()) throw NoUploadFile();
|
||||
if (!QFile(_nextFile).exists()) throw FileNotFound(_nextFile);
|
||||
QString filename(_nextFile);
|
||||
_nextFile.clear();
|
||||
return filename;
|
||||
} else {
|
||||
QString filename(QFileDialog::getOpenFileName
|
||||
(frame->page()->view(), tr("File to Upload")));
|
||||
if (filename.size()) uploadFile(filename);
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
private:
|
||||
bool _unattended;
|
||||
QString _nextFile;
|
||||
};
|
||||
|
||||
#endif
|
178
src/webrunner.cxx
Normal file
178
src/webrunner.cxx
Normal file
@@ -0,0 +1,178 @@
|
||||
#include <commands.hxx>
|
||||
#include <webrunner.hxx>
|
||||
#include <webpage.hxx>
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QWebView>
|
||||
#include <QStringList>
|
||||
#include <QCommandLineParser>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <xml-cxx/xml.hxx>
|
||||
#include <mrw/string.hxx>
|
||||
|
||||
std::string VERSION("0.9.4");
|
||||
|
||||
QString format(QString txt, int indent = 2, int cpl = 60) {
|
||||
QStringList res;
|
||||
QStringList lines(txt.split('\n'));
|
||||
QString ind(indent, ' ');
|
||||
Q_FOREACH(QString line, lines) {
|
||||
line.insert(0, ind);
|
||||
for (int pos(0); line.size()-pos>cpl; ++pos) {
|
||||
pos=line.lastIndexOf(' ', pos+cpl);
|
||||
line.remove(pos, 1);
|
||||
line.insert(pos, "\n"+ind);
|
||||
}
|
||||
res+=line;
|
||||
}
|
||||
return res.join('\n');
|
||||
}
|
||||
|
||||
QString help(const Script& s) {
|
||||
std::ostringstream ss;
|
||||
ss<<"Synopsis"<<std::endl
|
||||
<<std::endl
|
||||
<<" webrunner [<OPTIONS>] [<file1> [<file2> [...]]]"<<std::endl
|
||||
<<std::endl
|
||||
<<"Script Syntax"<<std::endl
|
||||
<<std::endl
|
||||
<<format(s.syntax()).toStdString()<<std::endl
|
||||
<<std::endl
|
||||
<<"Script Commands"<<std::endl
|
||||
<<std::endl
|
||||
<<format(s.commands()).toStdString()<<std::endl;
|
||||
return QString::fromStdString(ss.str());
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) try {
|
||||
bool failed(false);
|
||||
QApplication a(argc, argv);
|
||||
QWebView p;
|
||||
p.setPage(new TestWebPage(&p, true));
|
||||
QCommandLineParser parser;
|
||||
Script script;
|
||||
parser.setApplicationDescription(help(script));
|
||||
parser.addHelpOption();
|
||||
parser.addOption(QCommandLineOption
|
||||
(QStringList()<<"x"<<"xml",
|
||||
"store XML output in <xmlfile>", "xmlfile"));
|
||||
parser.addOption(QCommandLineOption
|
||||
(QStringList()<<"r"<<"retries",
|
||||
"on error retry up to <maxretries> times",
|
||||
"maxretries", "0"));
|
||||
parser.addOption(QCommandLineOption
|
||||
(QStringList()<<"W"<<"width",
|
||||
"set screenshot size to <width> pixel", "width", "2048"));
|
||||
parser.addOption(QCommandLineOption
|
||||
(QStringList()<<"H"<<"height",
|
||||
"set screenshot size to <height> pixel", "height", "2048"));
|
||||
parser.addOption(QCommandLineOption
|
||||
(QStringList()<<"v"<<"version",
|
||||
"show version information"));
|
||||
parser.addOption(QCommandLineOption
|
||||
(QStringList()<<"s"<<"skipped",
|
||||
"treat skipped test cases as failure in XML output file"));
|
||||
parser.addOption(QCommandLineOption
|
||||
(QStringList()<<"t"<<"target-path",
|
||||
"set screenshot target path to <path>", "path",
|
||||
QDir().absolutePath()+QDir::separator()+"attachments"));
|
||||
parser.process(a);
|
||||
if (parser.isSet("version")) {
|
||||
std::cout<<*argv<<" "<<VERSION<<std::endl;
|
||||
return 0;
|
||||
}
|
||||
int retries(parser.value("retries").toInt());
|
||||
int width(parser.value("width").toInt());
|
||||
int height(parser.value("height").toInt());
|
||||
QString target(parser.value("target-path"));
|
||||
p.resize(width, height);
|
||||
xml::Node testsuites("testsuites");
|
||||
Q_FOREACH(QString file, parser.positionalArguments()) {
|
||||
int expectedtestcases(-1);
|
||||
xml::Node testsuite("testsuite");
|
||||
testsuite.attr("name") = QFileInfo(file).baseName().toStdString();
|
||||
testsuite.attr("timestamp") =
|
||||
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
|
||||
xml::Node testcase("testcase");
|
||||
testcase.attr("classname") = "testsuite-preparation";
|
||||
try {
|
||||
script.reset();
|
||||
script.log("=====================");
|
||||
script.log("TEST: "+file);
|
||||
script.log("---------------------");
|
||||
testcase.attr("name") = "open test suite file";
|
||||
testsuite<<testcase;
|
||||
QFile f(file);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
throw std::runtime_error("cannot open file "+file.toStdString());
|
||||
QString txt(QString::fromUtf8(f.readAll()));
|
||||
testcase.attr("name") = "parse test suite file";
|
||||
testsuite<<testcase;
|
||||
script.parse(txt.split('\n'));
|
||||
expectedtestcases = script.steps()+2;
|
||||
if (failed) {
|
||||
script.log("FAILED: "+file+" skipped due to previous error");
|
||||
testcase.attr("name") = "testsuite";
|
||||
xml::Node failure("failure");
|
||||
failure.attr("message") = "ignored due to previous failure";
|
||||
testsuite<<(testcase<<failure);
|
||||
testsuite.attr("failures") =
|
||||
mrw::string(expectedtestcases+1-testsuite.children());
|
||||
if (parser.isSet("skipped")) {
|
||||
testcase.attr("name") = "skipped test case";
|
||||
failure.attr("message") = "skipped due to previous failure";
|
||||
testcase<<failure;
|
||||
for (int i(testsuite.children()); i<expectedtestcases; ++i)
|
||||
testsuite<<testcase;
|
||||
}
|
||||
} else {
|
||||
script.run(p.page()->mainFrame(), testsuite,
|
||||
target+QDir::separator()+QFileInfo(file).baseName(),
|
||||
true, retries);
|
||||
testsuite.attr("failures") = "0";
|
||||
script.log("SUCCESS: "+file);
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
script.log("FAILED: "+file+" with "+e.what());
|
||||
xml::Node failure("failure");
|
||||
failure.attr("message") = Script::xmlattr(e.what()).toStdString();
|
||||
testsuite[testsuite.children()-1]<<failure;
|
||||
if (expectedtestcases==-1)
|
||||
testsuite.attr("failures") = "1";
|
||||
else
|
||||
testsuite.attr("failures") =
|
||||
mrw::string(expectedtestcases+1-testsuite.children());
|
||||
if (parser.isSet("skipped")) {
|
||||
testcase.attr("name") = "skipped test case";
|
||||
failure.attr("message") = "skipped due to previous failure";
|
||||
testcase<<failure;
|
||||
for (int i(testsuite.children()); i<expectedtestcases; ++i)
|
||||
testsuite<<testcase;
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
if (expectedtestcases==-1) {
|
||||
testsuite.attr("tests") = mrw::string(testsuite.children());
|
||||
} else {
|
||||
testsuite.attr("tests") = mrw::string(expectedtestcases);
|
||||
}
|
||||
if (script.cout().size())
|
||||
testsuite<<(xml::String("system-out") =
|
||||
script.xmlattr(script.cout()).toStdString());
|
||||
if (script.cerr().size())
|
||||
testsuite<<(xml::String("system-err") =
|
||||
script.xmlattr(script.cerr()).toStdString());
|
||||
testsuites<<testsuite;
|
||||
}
|
||||
if (parser.isSet("xml")) { // todo: write xml file
|
||||
std::ofstream xmlfile(parser.value("xml").toStdString());
|
||||
xmlfile<<testsuites<<std::endl;
|
||||
}
|
||||
return failed ? 1 : 0;
|
||||
} catch (std::exception& e) {
|
||||
return 1;
|
||||
}
|
9
src/webtester.cxx
Normal file
9
src/webtester.cxx
Normal file
@@ -0,0 +1,9 @@
|
||||
#include <testgui.hxx>
|
||||
#include <QApplication>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QApplication a(argc, argv);
|
||||
TestGUI w(0, argc>1?argv[1]:"");
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
30
src/webtester.pro
Normal file
30
src/webtester.pro
Normal file
@@ -0,0 +1,30 @@
|
||||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2014-11-26T10:04:37
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += core webkitwidgets
|
||||
CONFIG += C++11
|
||||
|
||||
CODECFORSRC = UTF-8
|
||||
CODECFORTR = UTF-8
|
||||
|
||||
QMAKE_LIBS+=-lxml-cxx
|
||||
|
||||
webtester {
|
||||
QT += gui
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
TEMPLATE = app
|
||||
TARGET = webtester
|
||||
SOURCES = webtester.cxx
|
||||
HEADERS = testgui.hxx commands.hxx webpage.hxx networkaccessmanager.hxx
|
||||
FORMS = testgui.ui
|
||||
}
|
||||
|
||||
webrunner {
|
||||
TEMPLATE = app
|
||||
TARGET = webrunner
|
||||
SOURCES = webrunner.cxx
|
||||
HEADERS = commands.hxx webpage.hxx networkaccessmanager.hxx
|
||||
}
|
Reference in New Issue
Block a user