parent
a928df3fc7
commit
5f27a82624
10 changed files with 3551 additions and 0 deletions
@ -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 |
@ -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 |
@ -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 |
@ -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
|
@ -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> |
@ -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> |
@ -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 |
@ -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; |
||||
} |
@ -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(); |
||||
} |
@ -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 |
||||
} |
Loading…
Reference in new issue