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