Test your websites with this simple GUI based scripted webtester. Generate simple testscripts directly from surfng on the webpage, enhance them with your commands, with variables, loops, checks, … and finally run automated web tests.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1511 lines
49 KiB
1511 lines
49 KiB
/*! @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; |
|
} |
|
static void realMouseClick(QWebFrame* frame, QString selector) { |
|
QWebElement element(find(frame, selector)); |
|
if (element.isNull()) throw ElementNotFound(selector); |
|
realMouseClick(element); |
|
} |
|
static void realMouseClick(const QWebElement& element) { |
|
QWidget* web(element.webFrame()->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; |
|
element.webFrame()->setScrollBarValue(Qt::Horizontal, pixelsToScrolRight); |
|
element.webFrame()->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(); |
|
} |
|
static void sleep(int s) { |
|
QTime dieTime= QTime::currentTime().addSecs(s); |
|
while (QTime::currentTime()<dieTime) |
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100); |
|
} |
|
public: |
|
static QWebElement find(QWebFrame* frame, QString selector, |
|
int repeat = 2, int sleepsec = 1) { |
|
QWebElement element; |
|
element = find1(frame, selector, repeat, sleepsec); |
|
if (!element.isNull()) return element; |
|
element = find1(frame->page()->currentFrame(), selector, |
|
repeat, sleepsec); |
|
if (!element.isNull()) return element; |
|
element = find1(frame->page()->mainFrame(), selector, |
|
repeat, sleepsec); |
|
if (!element.isNull()) return element; |
|
return element; |
|
} |
|
static QWebElement find1(QWebFrame* frame, QString selector, |
|
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, 1, 0); |
|
if (!element.isNull()) return element; |
|
} |
|
if (sleepsec) sleep(sleepsec); |
|
} |
|
return element; |
|
} |
|
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("%4-%1-%2-%3.html") |
|
.arg(base) |
|
.arg(line, 4, 10, QChar('0')) |
|
.arg(name) |
|
.arg(QDateTime::currentDateTime() |
|
.toString("yyyyMMddHHmmss"))); |
|
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("%4-%1-%2-%3.png") |
|
.arg(base) |
|
.arg(line, 4, 10, QChar('0')) |
|
.arg(name) |
|
.arg(QDateTime::currentDateTime() |
|
.toString("yyyyMMddHHmmss"))); |
|
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)); |
|
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)); |
|
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)); |
|
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)); |
|
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; |
|
}; |
|
|
|
|
|
class Certificate: public Command { |
|
public: |
|
QString tag() const { |
|
return ""; |
|
} |
|
QString description() const { |
|
return |
|
"certificate <filename>" |
|
"\n\n" |
|
"Load a CA certificate that will be accepted on SSL connections."; |
|
} |
|
QString command() const { |
|
return ""; |
|
} |
|
std::shared_ptr<Command> parse(Script*, QString args, |
|
QStringList&, int) { |
|
std::shared_ptr<Certificate> cmd(new (Certificate)); |
|
cmd->_filename = args.trimmed(); |
|
return cmd; |
|
} |
|
bool execute(Script* script, QWebFrame*) { |
|
Logger log(this, script); |
|
QString filename(script->replacevars(_filename)); |
|
QFile cacertfile(filename); |
|
if (!cacertfile.exists()) throw FileNotFound(filename); |
|
QSslCertificate cacert(&cacertfile); |
|
if (cacert.isNull()) throw NotACertificate(filename); |
|
QSslSocket::addDefaultCaCertificate(cacert); |
|
return true; |
|
} |
|
private: |
|
QString _filename; |
|
}; |
|
|
|
/* 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
|
|
|