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