More features: have a seperate setup script, where global variable replacements can be placed, new commandline parameters, command window, better recording, chose click type, ...
This commit is contained in:
@@ -29,6 +29,8 @@ if test -z "${QMAKE}"; then
|
|||||||
AC_MSG_ERROR([cannot find a qmake command])
|
AC_MSG_ERROR([cannot find a qmake command])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# requires mrw-c++ - to be defined ...
|
||||||
|
|
||||||
README=$(tail -n +3 README)
|
README=$(tail -n +3 README)
|
||||||
README_DEB=$(tail -n +3 README | sed -e 's/^$/./g' -e 's/^/ /g')
|
README_DEB=$(tail -n +3 README | sed -e 's/^$/./g' -e 's/^/ /g')
|
||||||
DESCRIPTION=$(head -1 README)
|
DESCRIPTION=$(head -1 README)
|
||||||
|
15
docker/Dockerfile
Normal file
15
docker/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM ubuntu:latest
|
||||||
|
MAINTAINER "Marc Wäckerlin"
|
||||||
|
|
||||||
|
RUN apt-get install -y wget software-properties-common apt-transport-https
|
||||||
|
RUN apt-add-repository https://dev.marc.waeckerlin.org/repository
|
||||||
|
RUN wget -O- https://dev.marc.waeckerlin.org/repository/PublicKey | apt-key add -
|
||||||
|
RUN apt-get update -y
|
||||||
|
RUN apt-get install -y xvfb webtester
|
||||||
|
|
||||||
|
ADD runtests.sh runtests.sh
|
||||||
|
|
||||||
|
VOLUME /tests
|
||||||
|
|
||||||
|
WORKDIR /tests
|
||||||
|
CMD /runtests.sh
|
11
docker/runtests.sh
Executable file
11
docker/runtests.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
if test -z "${WEBRUNNER_SCRIPTS}"; then
|
||||||
|
echo '*** ERROR: set at least -e WEBRUNNER_SCRIPTS="testfile.wt ..."'
|
||||||
|
echo " optional variables: XVFB_SERVER_ARGS, WEBRUNNER_ARGS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
xvfb-run -a --server-args="${XVFB_SERVER_ARGS:--screen 0 2048x2048x24}" \
|
||||||
|
webrunner ${WEBRUNNER_ARGS:--W 2048 -H 2048 -sx testoutput.xml} \
|
||||||
|
${WEBRUNNER_SCRIPTS}
|
344
src/commands.hxx
344
src/commands.hxx
@@ -21,6 +21,7 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -35,8 +36,8 @@ class Command;
|
|||||||
class Logger {
|
class Logger {
|
||||||
public:
|
public:
|
||||||
Logger(Command* command, Script* script);
|
Logger(Command* command, Script* script);
|
||||||
void plainlog(QString txt);
|
void operator[](QString txt);
|
||||||
void log(QString txt);
|
void operator()(QString txt);
|
||||||
~Logger();
|
~Logger();
|
||||||
private:
|
private:
|
||||||
Command* _command;
|
Command* _command;
|
||||||
@@ -170,7 +171,7 @@ class Empty: public Command {
|
|||||||
"Empty lines are allowed";
|
"Empty lines are allowed";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "";
|
return tag();
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
||||||
std::shared_ptr<Empty> cmd(new Empty());
|
std::shared_ptr<Empty> cmd(new Empty());
|
||||||
@@ -258,14 +259,14 @@ class Screenshot: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"screenshot <filename-base>"
|
tag()+" <filename-base>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Create a PNG screenshot of the actual web page and store it in the "
|
"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 "
|
"file <filename-base>.png. If not already opened, a browser window "
|
||||||
"will pop up to take the screenshot.";
|
"will pop up to take the screenshot.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "screenshot "+_filename;
|
return tag()+" "+_filename;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
std::shared_ptr<Screenshot> cmd(new Screenshot());
|
std::shared_ptr<Screenshot> cmd(new Screenshot());
|
||||||
@@ -325,6 +326,25 @@ class Script: public QObject {
|
|||||||
void logging(QString);
|
void logging(QString);
|
||||||
public:
|
public:
|
||||||
typedef std::pair<QString, QStringList> Signal;
|
typedef std::pair<QString, QStringList> Signal;
|
||||||
|
enum ClickType {
|
||||||
|
REAL_MOUSE_CLICK,
|
||||||
|
JAVASCRIPT_CLICK
|
||||||
|
};
|
||||||
|
enum Formatting {
|
||||||
|
PLAIN,
|
||||||
|
HTML
|
||||||
|
};
|
||||||
|
/// QString that sorts first by string length, then by content
|
||||||
|
class LenString: public QString {
|
||||||
|
public:
|
||||||
|
LenString() {}
|
||||||
|
LenString(const QString& o): QString(o) {}
|
||||||
|
virtual bool operator<(const LenString& o) const {
|
||||||
|
return
|
||||||
|
size()<o.size() ? true : o.size()<size() ? false
|
||||||
|
: QString::operator<(o.toLatin1());
|
||||||
|
}
|
||||||
|
};
|
||||||
public:
|
public:
|
||||||
static QString xmlattr(QString attr, bool br = false) {
|
static QString xmlattr(QString attr, bool br = false) {
|
||||||
attr.replace("&", "&")//.replace(" ", " ")
|
attr.replace("&", "&")//.replace(" ", " ")
|
||||||
@@ -343,7 +363,7 @@ class Script: public QObject {
|
|||||||
.replace(" ", " ").replace("&", "&");
|
.replace(" ", " ").replace("&", "&");
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
Script() {
|
Script(): _clicktype(JAVASCRIPT_CLICK) {
|
||||||
initPrototypes();
|
initPrototypes();
|
||||||
}
|
}
|
||||||
QString syntax() const {
|
QString syntax() const {
|
||||||
@@ -355,10 +375,34 @@ class Script: public QObject {
|
|||||||
"Note: When a selector is required as parameter, then the selector "
|
"Note: When a selector is required as parameter, then the selector "
|
||||||
"is a CSS selector that must not contain spaces.";
|
"is a CSS selector that must not contain spaces.";
|
||||||
}
|
}
|
||||||
QString commands() const {
|
QString commands(Formatting f = PLAIN) const {
|
||||||
QString cmds;
|
QString cmds;
|
||||||
for (auto it(_prototypes.begin()); it!=_prototypes.end(); ++it)
|
for (auto it(_prototypes.begin()); it!=_prototypes.end(); ++it)
|
||||||
cmds+="\n\n"+it->second->description();
|
switch (f) {
|
||||||
|
case PLAIN: {
|
||||||
|
cmds+="\n\n\nCOMMAND: "+it->first+"\n\n"+it->second->description();
|
||||||
|
} break;
|
||||||
|
case HTML: {
|
||||||
|
cmds+="<h1>"+it->first+"</h1><p>"
|
||||||
|
+it->second->description()
|
||||||
|
.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace(QRegularExpression("<([^ ]+)>"),
|
||||||
|
"<i>\\1</i>")
|
||||||
|
.replace(QRegularExpression("(\n[^ ][^\n]*)(\n +-)"),
|
||||||
|
"\\1<ul>\\2")
|
||||||
|
.replace(QRegularExpression("(\n +-[^\n]*\n)([^ ])"),
|
||||||
|
"\\1</ul>\\2")
|
||||||
|
.replace(QRegularExpression("\n +- ([^\n]*)(</ul>)?"),
|
||||||
|
"<li>\\1</li>\\2")
|
||||||
|
.replace("</li>\n", "</li>")
|
||||||
|
.replace("\n ", "\n ")
|
||||||
|
.replace("\n\n", "</p><p>")
|
||||||
|
.replace("\n", "<br/>")
|
||||||
|
+"</p>";
|
||||||
|
} break;
|
||||||
|
}
|
||||||
return cmds.trimmed();
|
return cmds.trimmed();
|
||||||
}
|
}
|
||||||
void reset() {
|
void reset() {
|
||||||
@@ -368,6 +412,14 @@ class Script: public QObject {
|
|||||||
_ignores.clear();
|
_ignores.clear();
|
||||||
_cout.clear();
|
_cout.clear();
|
||||||
_cerr.clear();
|
_cerr.clear();
|
||||||
|
_ignoreSignalsUntil.clear();
|
||||||
|
}
|
||||||
|
void cleanup() {
|
||||||
|
reset();
|
||||||
|
_variables.clear();
|
||||||
|
_rvariables.clear();
|
||||||
|
_timeout = 20;
|
||||||
|
_clicktype = JAVASCRIPT_CLICK;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(QStringList& in, int linenr) try {
|
std::shared_ptr<Command> parse(QStringList& in, int linenr) try {
|
||||||
QString line(in.takeFirst().trimmed());
|
QString line(in.takeFirst().trimmed());
|
||||||
@@ -555,19 +607,36 @@ class Script: public QObject {
|
|||||||
}
|
}
|
||||||
void set(QString name, QString value) {
|
void set(QString name, QString value) {
|
||||||
_variables[name] = value;
|
_variables[name] = value;
|
||||||
|
_rvariables[value] = name;
|
||||||
}
|
}
|
||||||
void unset(QString name) {
|
void unset(QString name) {
|
||||||
|
_rvariables.remove(_variables[name]);
|
||||||
_variables.remove(name);
|
_variables.remove(name);
|
||||||
}
|
}
|
||||||
void timeout(int t) {
|
void timeout(int t) {
|
||||||
_timeout = t;
|
_timeout = t;
|
||||||
}
|
}
|
||||||
|
void clicktype(ClickType c) {
|
||||||
|
_clicktype = c;
|
||||||
|
}
|
||||||
|
ClickType clicktype() {
|
||||||
|
return _clicktype;
|
||||||
|
}
|
||||||
QString replacevars(QString txt) {
|
QString replacevars(QString txt) {
|
||||||
for(QMap<QString, QString>::iterator it(_variables.begin());
|
for(QMap<QString, QString>::iterator it(_variables.begin());
|
||||||
it!=_variables.end(); ++it)
|
it!=_variables.end(); ++it)
|
||||||
txt.replace(it.key(), it.value());
|
txt.replace(it.key(), it.value());
|
||||||
return txt;
|
return txt;
|
||||||
}
|
}
|
||||||
|
QString insertvars(QString txt) {
|
||||||
|
QMapIterator<LenString, LenString> it(_rvariables);
|
||||||
|
it.toBack();
|
||||||
|
while (it.hasPrevious()) {
|
||||||
|
it.previous();
|
||||||
|
txt.replace(it.key(), it.value());
|
||||||
|
}
|
||||||
|
return txt;
|
||||||
|
}
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void log(QString text) {
|
void log(QString text) {
|
||||||
text = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss ")+text;
|
text = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss ")+text;
|
||||||
@@ -703,8 +772,10 @@ class Script: public QObject {
|
|||||||
QString _cerr;
|
QString _cerr;
|
||||||
bool _screenshots;
|
bool _screenshots;
|
||||||
QString _ignoreSignalsUntil;
|
QString _ignoreSignalsUntil;
|
||||||
QMap<QString, QString> _variables;
|
QMap<QString, QString> _variables; ///< variable mapping
|
||||||
|
QMap<LenString, LenString> _rvariables; ///< reverse variable mapping
|
||||||
int _timeout;
|
int _timeout;
|
||||||
|
ClickType _clicktype;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Do: public Command {
|
class Do: public Command {
|
||||||
@@ -714,7 +785,7 @@ class Do: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"do <selector>\n <javascript-line1>\n <javascript-line2>"
|
tag()+" <selector>\n <javascript-line1>\n <javascript-line2>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Execute JavaScript on a CSS selected object. The object is the first "
|
"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 "
|
"object in the DOM tree that matches the given CSS selector. You can "
|
||||||
@@ -723,7 +794,7 @@ class Do: public Command {
|
|||||||
"one space";
|
"one space";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "do "+_selector+_javascript;
|
return tag()+" "+_selector+_javascript;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args,
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
QStringList& in, int) {
|
QStringList& in, int) {
|
||||||
@@ -735,8 +806,9 @@ class Do: public Command {
|
|||||||
}
|
}
|
||||||
bool execute(Script* script, QWebFrame* frame) {
|
bool execute(Script* script, QWebFrame* frame) {
|
||||||
Logger log(this, script);
|
Logger log(this, script);
|
||||||
QWebElement element(find(frame, _selector));
|
QWebElement element(find(frame, script->replacevars(_selector)));
|
||||||
if (element.isNull()) throw ElementNotFound(_selector);
|
if (element.isNull())
|
||||||
|
throw ElementNotFound(script->replacevars(_selector));
|
||||||
_result =
|
_result =
|
||||||
element.evaluateJavaScript(script->replacevars(_javascript)).toString();
|
element.evaluateJavaScript(script->replacevars(_javascript)).toString();
|
||||||
return true;
|
return true;
|
||||||
@@ -753,12 +825,12 @@ class Load: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"load <url>"
|
tag()+" <url>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Load an URL, the URL is given as parameter in full syntax.";
|
"Load an URL, the URL is given as parameter in full syntax.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "load "+_url;
|
return tag()+" "+_url;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
std::shared_ptr<Load> cmd(new Load());
|
std::shared_ptr<Load> cmd(new Load());
|
||||||
@@ -781,7 +853,7 @@ class Expect: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"expect <signal> [<parameter>]"
|
tag()+" <signal> [<parameter>]"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Expect a signal. Signals are emitted by webkit and may contain "
|
"Expect a signal. Signals are emitted by webkit and may contain "
|
||||||
"parameter. If a parameter is given in the script, then the parameter "
|
"parameter. If a parameter is given in the script, then the parameter "
|
||||||
@@ -800,7 +872,7 @@ class Expect: public Command {
|
|||||||
" - loadFinished true";
|
" - loadFinished true";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "expect "+_signal._signal
|
return tag()+" "+_signal._signal
|
||||||
+(_signal._args.size()?" "+_signal._args.join(' '):QString());
|
+(_signal._args.size()?" "+_signal._args.join(' '):QString());
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
@@ -851,12 +923,12 @@ class Open: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"open"
|
tag()+
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Open the browser window, so you can follow the test steps visually.";
|
"Open the browser window, so you can follow the test steps visually.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "open";
|
return tag();
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
||||||
std::shared_ptr<Open> cmd(new Open());
|
std::shared_ptr<Open> cmd(new Open());
|
||||||
@@ -876,14 +948,14 @@ class Sleep: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"sleep <seconds>"
|
tag()+" <seconds>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Sleep for a certain amount of seconds. This helps, if you must wait "
|
"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 "
|
"for some javascript actions, i.e. AJAX or slow pages, and the "
|
||||||
"excpeted signals are not sufficient.";
|
"excpeted signals are not sufficient.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "sleep "+_time;
|
return tag()+" "+_time;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString time, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString time, QStringList&, int) {
|
||||||
std::shared_ptr<Sleep> cmd(new Sleep());
|
std::shared_ptr<Sleep> cmd(new Sleep());
|
||||||
@@ -913,14 +985,14 @@ class Exit: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"exit"
|
tag()+
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Successfully terminate script immediately. The following commands "
|
"Successfully terminate script immediately. The following commands "
|
||||||
"are not executed. This helps when you debug your scripts and you "
|
"are not executed. This helps when you debug your scripts and you "
|
||||||
"want the script stop at a certain point for investigations.";
|
"want the script stop at a certain point for investigations.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "exit";
|
return tag();
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString, QStringList&, int) {
|
||||||
std::shared_ptr<Exit> cmd(new Exit());
|
std::shared_ptr<Exit> cmd(new Exit());
|
||||||
@@ -939,7 +1011,7 @@ class IgnoreTo: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"ignoreto <label>"
|
tag()+" <label>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Ignore all following commands up to a given label. The following "
|
"Ignore all following commands up to a given label. The following "
|
||||||
"commands are not executed until the given label appears in the "
|
"commands are not executed until the given label appears in the "
|
||||||
@@ -947,7 +1019,7 @@ class IgnoreTo: public Command {
|
|||||||
"want to skip some lines of script code.";
|
"want to skip some lines of script code.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "ignoreto "+_label;
|
return tag()+" "+_label;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
std::shared_ptr<IgnoreTo> cmd(new IgnoreTo());
|
std::shared_ptr<IgnoreTo> cmd(new IgnoreTo());
|
||||||
@@ -971,12 +1043,12 @@ class Label: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"label <label>"
|
tag()+" <label>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"This marks the label refered by command \"ignoreto\".";
|
"This marks the label refered by command \"ignoreto\".";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "label "+_label;
|
return tag()+" "+_label;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
std::shared_ptr<Label> cmd(new Label());
|
std::shared_ptr<Label> cmd(new Label());
|
||||||
@@ -1000,23 +1072,23 @@ class Upload: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"upload <selector> -> <filename>"
|
tag()+" <selector> -> <filename>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Presses the specified file upload button and passes a given file "
|
"Presses the specified file upload button and passes a given file "
|
||||||
"name. The command requires a CSS selector followed by a filename. "
|
"name. The command requires a CSS selector followed by a filename. "
|
||||||
"The first object that matches the selector is used.";
|
"The first object that matches the selector is used.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "upload "+_selector+" -> "+_filename;
|
return tag()+" "+_selector+" -> "+_filename;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
std::shared_ptr<Upload> cmd(new Upload());
|
std::shared_ptr<Upload> cmd(new Upload());
|
||||||
QStringList allargs(args.split("->"));
|
QStringList allargs(args.split("->"));
|
||||||
if (allargs.size()<2)
|
if (allargs.size()<2)
|
||||||
throw BadArgument("upload needs a selector folowed by a filename, "
|
throw BadArgument(tag()+"requires <selector> -> <filename>, "
|
||||||
"instead of: \""+args+"\"");
|
"instead of: \""+args+"\"");
|
||||||
cmd->_selector = allargs.takeFirst().trimmed();
|
cmd->_selector = allargs.takeFirst().trimmed();
|
||||||
cmd->_filename = allargs.join(" ").trimmed();
|
cmd->_filename = allargs.join("->").trimmed();
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
bool execute(Script* script, QWebFrame* frame) {
|
bool execute(Script* script, QWebFrame* frame) {
|
||||||
@@ -1047,7 +1119,7 @@ class Exists: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"exists <selector> -> <text>"
|
tag()+" <selector> -> <text>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Assert that a certain text exists in the selected object, or if no "
|
"Assert that a certain text exists in the selected object, or if no "
|
||||||
"text is given, assert that the specified object exists. The object "
|
"text is given, assert that the specified object exists. The object "
|
||||||
@@ -1055,7 +1127,7 @@ class Exists: public Command {
|
|||||||
"text.";
|
"text.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "exists "+_selector+(_text.size()?" -> "+_text:QString());
|
return tag()+" "+_selector+(_text.size()?" -> "+_text:QString());
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
std::shared_ptr<Exists> cmd(new Exists());
|
std::shared_ptr<Exists> cmd(new Exists());
|
||||||
@@ -1064,7 +1136,7 @@ class Exists: public Command {
|
|||||||
cmd->_selector = args;
|
cmd->_selector = args;
|
||||||
} else {
|
} else {
|
||||||
cmd->_selector = allargs.takeFirst().trimmed();
|
cmd->_selector = allargs.takeFirst().trimmed();
|
||||||
cmd->_text = allargs.join(" ").trimmed();
|
cmd->_text = allargs.join("->").trimmed();
|
||||||
}
|
}
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
@@ -1099,7 +1171,7 @@ class Not: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"not <selector> -> <text>"
|
tag()+" <selector> -> <text>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Assert that a certain text does not exists in the selected object, "
|
"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 "
|
"or if no text is given, assert that the specified object does not "
|
||||||
@@ -1107,7 +1179,7 @@ class Not: public Command {
|
|||||||
"are search for the text.";
|
"are search for the text.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "not "+_selector+(_text.size()?" -> "+_text:QString());
|
return tag()+" "+_selector+(_text.size()?" -> "+_text:QString());
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
std::shared_ptr<Not> cmd(new Not());
|
std::shared_ptr<Not> cmd(new Not());
|
||||||
@@ -1116,7 +1188,7 @@ class Not: public Command {
|
|||||||
cmd->_selector = args;
|
cmd->_selector = args;
|
||||||
} else {
|
} else {
|
||||||
cmd->_selector = allargs.takeFirst().trimmed();
|
cmd->_selector = allargs.takeFirst().trimmed();
|
||||||
cmd->_text = allargs.join(" ").trimmed();
|
cmd->_text = allargs.join("->").trimmed();
|
||||||
}
|
}
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
@@ -1145,7 +1217,7 @@ class Execute: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"execute <command>\n <line1>\n <line2>\n <...>"
|
tag()+" <command>\n <line1>\n <line2>\n <...>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Execute <command>. The command can have space separated arguments. "
|
"Execute <command>. The command can have space separated arguments. "
|
||||||
"Following lines that are intended by at least "
|
"Following lines that are intended by at least "
|
||||||
@@ -1154,8 +1226,8 @@ class Execute: public Command {
|
|||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
QStringList script(_script);
|
QStringList script(_script);
|
||||||
script.replaceInStrings(QRegExp("^"), " ");
|
script.replaceInStrings(QRegularExpression("^"), " ");
|
||||||
return "execute "+_command
|
return tag()+" "+_command
|
||||||
+(_args.size()?" "+_args.join(' '):QString())
|
+(_args.size()?" "+_args.join(' '):QString())
|
||||||
+(script.size()?"\n"+script.join("\n"):QString());
|
+(script.size()?"\n"+script.join("\n"):QString());
|
||||||
}
|
}
|
||||||
@@ -1214,15 +1286,16 @@ class Download: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"download <filename>"
|
tag()+" <filename>"
|
||||||
"<command-to-start-download>"
|
"<command-to-start-download>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Set download file before loading a download link or clicking on a "
|
"Set download file before loading a download link or clicking on a "
|
||||||
"download button. The next line must be exactly one command that "
|
"download button. The next line must be exactly one command that "
|
||||||
"initiates the download.";
|
"initiates the download.\n\n"
|
||||||
|
"Please note that variables are not substituted in the filename.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "download"+(_filename.size()?" "+_filename:QString())+"\n"
|
return tag()+(_filename.size()?" "+_filename:QString())+"\n"
|
||||||
+_next->command();
|
+_next->command();
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script* script, QString args,
|
std::shared_ptr<Command> parse(Script* script, QString args,
|
||||||
@@ -1243,11 +1316,11 @@ class Download: public Command {
|
|||||||
script->timer().stop(); // no timeout during download
|
script->timer().stop(); // no timeout during download
|
||||||
for (_done = false; !_done;) // wait for download finish
|
for (_done = false; !_done;) // wait for download finish
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||||
log.log("download terminated "+
|
log("download terminated "+
|
||||||
QString(_netsuccess&&_filesuccess?"successfully":"with error"));
|
QString(_netsuccess&&_filesuccess?"successfully":"with error"));
|
||||||
if (!_netsuccess) throw DownloadFailed(_realfilename);
|
if (!_netsuccess) throw DownloadFailed(_realfilename);
|
||||||
if (!_filesuccess) throw WriteFileFailed(_realfilename);
|
if (!_filesuccess) throw WriteFileFailed(_realfilename);
|
||||||
log.plainlog("[[ATTACHMENT|"+QDir(_realfilename).absolutePath()+"]]");
|
log["[[ATTACHMENT|"+QDir(_realfilename).absolutePath()+"]]"];
|
||||||
disconnect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
|
disconnect(frame->page(), SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||||
this, SLOT(unsupportedContent(QNetworkReply*)));
|
this, SLOT(unsupportedContent(QNetworkReply*)));
|
||||||
return res;
|
return res;
|
||||||
@@ -1271,8 +1344,8 @@ class Download: public Command {
|
|||||||
.isValid()) {
|
.isValid()) {
|
||||||
QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
|
QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
|
||||||
.toString());
|
.toString());
|
||||||
if (part.contains(QRegExp("attachment; *filename="))) {
|
if (part.contains(QRegularExpression("attachment; *filename="))) {
|
||||||
part.replace(QRegExp(".*attachment; *filename="), "");
|
part.replace(QRegularExpression(".*attachment; *filename="), "");
|
||||||
if (part.size()) _realfilename = part;
|
if (part.size()) _realfilename = part;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1293,12 +1366,12 @@ class Click: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"click <selector>"
|
tag()+" <selector>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Click on the specified element";
|
"Click on the specified element";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "click "+_selector;
|
return tag()+" "+_selector;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
std::shared_ptr<Command> parse(Script*, QString args, QStringList&, int) {
|
||||||
std::shared_ptr<Click> cmd(new Click());
|
std::shared_ptr<Click> cmd(new Click());
|
||||||
@@ -1307,7 +1380,22 @@ class Click: public Command {
|
|||||||
}
|
}
|
||||||
bool execute(Script* script, QWebFrame* frame) {
|
bool execute(Script* script, QWebFrame* frame) {
|
||||||
Logger log(this, script);
|
Logger log(this, script);
|
||||||
|
switch (script->clicktype()) {
|
||||||
|
case Script::REAL_MOUSE_CLICK: {
|
||||||
|
log("Script::REAL_MOUSE_CLICK");
|
||||||
realMouseClick(frame, script->replacevars(_selector));
|
realMouseClick(frame, script->replacevars(_selector));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Script::JAVASCRIPT_CLICK:
|
||||||
|
default: {
|
||||||
|
log("Script::JAVASCRIPT_CLICK");
|
||||||
|
QWebElement element(find(frame, script->replacevars(_selector)));
|
||||||
|
if (element.isNull())
|
||||||
|
throw ElementNotFound(script->replacevars(_selector));
|
||||||
|
_result = element.evaluateJavaScript("this.click();").toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
@@ -1321,8 +1409,8 @@ class Set: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"set <variable>=<value>\n"
|
tag()+" <variable>=<value>\n"+
|
||||||
"set <variable>\n"
|
tag()+" <variable>\n"
|
||||||
" <command>"
|
" <command>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Sets the value of a variable either to a constant, or to the output"
|
"Sets the value of a variable either to a constant, or to the output"
|
||||||
@@ -1332,9 +1420,9 @@ class Set: public Command {
|
|||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
if (_next)
|
if (_next)
|
||||||
return "set "+_name+"\n "+_next->command();
|
return tag()+" "+_name+"\n "+_next->command();
|
||||||
else
|
else
|
||||||
return "set "+_name+" = "+_value;
|
return tag()+" "+_name+" = "+_value;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script* script, QString args,
|
std::shared_ptr<Command> parse(Script* script, QString args,
|
||||||
QStringList& in, int line) {
|
QStringList& in, int line) {
|
||||||
@@ -1374,12 +1462,12 @@ class UnSet: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"unset <variable>"
|
tag()+" <variable>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Undo the setting of a variable. The opposite of «set».";
|
"Undo the setting of a variable. The opposite of «set».";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "unset "+_name;
|
return tag()+" "+_name;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args,
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
QStringList&, int) {
|
QStringList&, int) {
|
||||||
@@ -1403,12 +1491,12 @@ class Timeout: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"timeout <seconds>"
|
tag()+" <seconds>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Set the timeout in seconds (defaults to 10).";
|
"Set the timeout in seconds (defaults to 10).";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "timeout "+_timeout;
|
return tag()+" "+_timeout;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args,
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
QStringList&, int) {
|
QStringList&, int) {
|
||||||
@@ -1436,12 +1524,12 @@ class CaCertificate: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"ca-certificate <filename.pem>"
|
tag()+" <filename.pem>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Load a CA certificate that will be accepted on SSL connections.";
|
"Load a CA certificate that will be accepted on SSL connections.";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "ca-certificate "+_filename;
|
return tag()+" "+_filename;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args,
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
QStringList&, int) {
|
QStringList&, int) {
|
||||||
@@ -1471,7 +1559,7 @@ class ClientCertificate: public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
"client-certificate <certfile.pem> <keyfile.key> <keypassword>"
|
tag()+" <certfile.pem> <keyfile.key> <keypassword>"
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"Load a client certificate to authenticate on SSL connections. "
|
"Load a client certificate to authenticate on SSL connections. "
|
||||||
"The password for the keyfile should not contain spaces. "
|
"The password for the keyfile should not contain spaces. "
|
||||||
@@ -1480,7 +1568,7 @@ class ClientCertificate: public Command {
|
|||||||
" openssl pkcs12 -in certfile.p12 -out keyfile.pem -nocerts\n";
|
" openssl pkcs12 -in certfile.p12 -out keyfile.pem -nocerts\n";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "client-certificate "+_certfile+" "+_keyfile+" "+_password;
|
return tag()+" "+_certfile+" "+_keyfile+" "+_password;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args,
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
QStringList&, int) {
|
QStringList&, int) {
|
||||||
@@ -1510,7 +1598,7 @@ class ClientCertificate: public Command {
|
|||||||
throw FileNotFound(filename);
|
throw FileNotFound(filename);
|
||||||
keyfile.open(QIODevice::ReadOnly);
|
keyfile.open(QIODevice::ReadOnly);
|
||||||
QSslKey k(&keyfile, QSsl::Rsa, QSsl::Pem,
|
QSslKey k(&keyfile, QSsl::Rsa, QSsl::Pem,
|
||||||
QSsl::PrivateKey, _password.toUtf8());
|
QSsl::PrivateKey, script->replacevars(_password).toUtf8());
|
||||||
if (k.isNull()) throw KeyNotReadable(filename);
|
if (k.isNull()) throw KeyNotReadable(filename);
|
||||||
sslConfig.setPrivateKey(k);
|
sslConfig.setPrivateKey(k);
|
||||||
QSslConfiguration::setDefaultConfiguration(sslConfig);
|
QSslConfiguration::setDefaultConfiguration(sslConfig);
|
||||||
@@ -1522,6 +1610,126 @@ class ClientCertificate: public Command {
|
|||||||
QString _password;
|
QString _password;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ClickType: public Command {
|
||||||
|
public:
|
||||||
|
QString tag() const {
|
||||||
|
return "clicktype";
|
||||||
|
}
|
||||||
|
QString description() const {
|
||||||
|
return
|
||||||
|
tag()+" <type>"
|
||||||
|
"\n\n"
|
||||||
|
"Set how mouseclicks should be mapped. The best solution depends on"
|
||||||
|
" your problem. Normally it is good to call \"click()\" on the element"
|
||||||
|
" using javascript. But with complex javascript infected websites, it"
|
||||||
|
" might be necessary to simulate a real mouse click.\n\n"
|
||||||
|
"<type> is one of:\n"
|
||||||
|
" - realmouse\n"
|
||||||
|
" - javascript (default)";
|
||||||
|
}
|
||||||
|
QString command() const {
|
||||||
|
switch (_clicktype) {
|
||||||
|
case Script::REAL_MOUSE_CLICK: return tag()+" realmouse";
|
||||||
|
case Script::JAVASCRIPT_CLICK: default:
|
||||||
|
return tag()+" javascript";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::shared_ptr<Command> parse(Script* script, QString args,
|
||||||
|
QStringList&, int) {
|
||||||
|
std::shared_ptr<ClickType> cmd(new ClickType());
|
||||||
|
QString choice(script->replacevars(args).trimmed());
|
||||||
|
if (choice=="realmouse")
|
||||||
|
cmd->_clicktype = Script::REAL_MOUSE_CLICK;
|
||||||
|
else if (choice=="javascript")
|
||||||
|
cmd->_clicktype = Script::JAVASCRIPT_CLICK;
|
||||||
|
else
|
||||||
|
throw BadArgument("unknown clicktype: "+choice);
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
bool execute(Script* script, QWebFrame* ) {
|
||||||
|
Logger log(this, script);
|
||||||
|
script->clicktype(_clicktype);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Script::ClickType _clicktype;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SetValue: public Command {
|
||||||
|
public:
|
||||||
|
QString tag() const {
|
||||||
|
return "setvalue";
|
||||||
|
}
|
||||||
|
QString description() const {
|
||||||
|
return
|
||||||
|
tag()+" <selector> -> '<value>'\n"+
|
||||||
|
tag()+" <selector> -> '<value1>', '<value2>', ..."
|
||||||
|
"\n\n"
|
||||||
|
"Set the selected element to a given value. It is mostly the same as"
|
||||||
|
" the following constuct, except that options in a select are evaluated"
|
||||||
|
" correctly:\n\n"
|
||||||
|
" do <selector>\n"
|
||||||
|
" this.value='<value>';\n\n"
|
||||||
|
"The second syntax with comma separated list of valus applies for"
|
||||||
|
" options in a select element\n\n"
|
||||||
|
"If you quote the values, then quote all values with the same"
|
||||||
|
" quotes. If you need a comma within a value, you must quote.";
|
||||||
|
}
|
||||||
|
QString command() const {
|
||||||
|
return tag()+" "+_selector+" -> "+_value;
|
||||||
|
}
|
||||||
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
|
QStringList&, int) {
|
||||||
|
std::shared_ptr<SetValue> cmd(new SetValue());
|
||||||
|
QStringList allargs(args.split("->"));
|
||||||
|
if (allargs.size()<2)
|
||||||
|
throw BadArgument(tag()+" requires <selector> -> <value>, "
|
||||||
|
"instead of: \""+args+"\"");
|
||||||
|
cmd->_selector = allargs.takeFirst().trimmed();
|
||||||
|
cmd->_value = allargs.join("->").trimmed();
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
bool execute(Script* script, QWebFrame* frame) {
|
||||||
|
Logger log(this, script);
|
||||||
|
QWebElement element(find(frame, script->replacevars(_selector)));
|
||||||
|
if (element.isNull())
|
||||||
|
throw ElementNotFound(script->replacevars(_selector));
|
||||||
|
QString value(script->replacevars(_value));
|
||||||
|
if (element.tagName()=="SELECT") {
|
||||||
|
// value is a comma seperated list of option values
|
||||||
|
QStringList values;
|
||||||
|
switch (value.size()>1&&value.at(0)==value.at(value.size()-1)
|
||||||
|
?value.at(0).toLatin1():'\0') {
|
||||||
|
case '"': case '\'': {
|
||||||
|
values = value.mid(1, value.size()-2)
|
||||||
|
.split(QRegularExpression(QString(value[0])+", *"
|
||||||
|
+QString(value[0])));
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
values = value.split(QRegularExpression(", *"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Q_FOREACH(QWebElement option, element.findAll("option")) {
|
||||||
|
QString name(option.evaluateJavaScript("this.value").toString());
|
||||||
|
option.evaluateJavaScript
|
||||||
|
("this.selected="+QString(values.contains(name)?"true;":"false;"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value.size()>1 && value[0]==value[value.size()-1] &&
|
||||||
|
(value[0]=='"' || value[0]=='\''))
|
||||||
|
element.evaluateJavaScript("this.value="+value+";");
|
||||||
|
else
|
||||||
|
element.evaluateJavaScript("this.value='"
|
||||||
|
+value.replace("'", "\\'")
|
||||||
|
+"';");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
QString _selector;
|
||||||
|
QString _value;
|
||||||
|
};
|
||||||
|
|
||||||
/* Template:
|
/* Template:
|
||||||
class : public Command {
|
class : public Command {
|
||||||
public:
|
public:
|
||||||
@@ -1530,12 +1738,12 @@ class : public Command {
|
|||||||
}
|
}
|
||||||
QString description() const {
|
QString description() const {
|
||||||
return
|
return
|
||||||
""
|
tag()+
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"";
|
"";
|
||||||
}
|
}
|
||||||
QString command() const {
|
QString command() const {
|
||||||
return "";
|
return tag();
|
||||||
}
|
}
|
||||||
std::shared_ptr<Command> parse(Script*, QString args,
|
std::shared_ptr<Command> parse(Script*, QString args,
|
||||||
QStringList& in, int) {
|
QStringList& in, int) {
|
||||||
@@ -1555,7 +1763,7 @@ inline bool Screenshot::execute(Script* script, QWebFrame* frame) {
|
|||||||
QString filename(screenshot(line(), targetdir(),
|
QString filename(screenshot(line(), targetdir(),
|
||||||
QFileInfo(testsuite()).baseName(),
|
QFileInfo(testsuite()).baseName(),
|
||||||
_filename, frame));
|
_filename, frame));
|
||||||
log.plainlog("[[ATTACHMENT|"+filename+"]]");
|
log["[[ATTACHMENT|"+filename+"]]"];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1566,11 +1774,11 @@ inline Logger::Logger(Command* command, Script* script):
|
|||||||
_script->log(_command->command());
|
_script->log(_command->command());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inline void Logger::log(QString txt) {
|
inline void Logger::operator()(QString txt) {
|
||||||
if (_command->log())
|
if (_command->log())
|
||||||
_script->log(txt);
|
_script->log(txt);
|
||||||
}
|
}
|
||||||
inline void Logger::plainlog(QString txt) {
|
inline void Logger::operator[](QString txt) {
|
||||||
_script->plainlog(txt);
|
_script->plainlog(txt);
|
||||||
}
|
}
|
||||||
inline Logger::~Logger() {
|
inline Logger::~Logger() {
|
||||||
@@ -1599,6 +1807,8 @@ inline void Script::initPrototypes() {
|
|||||||
add(new Timeout);
|
add(new Timeout);
|
||||||
add(new CaCertificate);
|
add(new CaCertificate);
|
||||||
add(new ClientCertificate);
|
add(new ClientCertificate);
|
||||||
|
add(new ::ClickType);
|
||||||
|
add(new SetValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
263
src/testgui.hxx
263
src/testgui.hxx
@@ -21,11 +21,16 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
|
#include <QTextDocumentFragment>
|
||||||
|
#include <mrw/stdext.hxx>
|
||||||
|
|
||||||
class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
public:
|
public:
|
||||||
explicit TestGUI(QWidget *parent = 0, QString url = QString()):
|
explicit TestGUI(QWidget *parent = 0,
|
||||||
|
QString url = QString(),
|
||||||
|
QString setupScript = QString(),
|
||||||
|
QString scriptFile = QString()):
|
||||||
QMainWindow(parent),
|
QMainWindow(parent),
|
||||||
_typing(false),
|
_typing(false),
|
||||||
_inEventFilter(false) {
|
_inEventFilter(false) {
|
||||||
@@ -40,18 +45,21 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
_web->setPage(page);
|
_web->setPage(page);
|
||||||
_web->installEventFilter(this); // track mouse and keyboard
|
_web->installEventFilter(this); // track mouse and keyboard
|
||||||
page->setForwardUnsupportedContent(true);
|
page->setForwardUnsupportedContent(true);
|
||||||
|
_commands->setText(Script().commands(Script::HTML));
|
||||||
connect(page, SIGNAL(uploadFile(QString)), SLOT(uploadFile(QString)));
|
connect(page, SIGNAL(uploadFile(QString)), SLOT(uploadFile(QString)));
|
||||||
connect(page, SIGNAL(unsupportedContent(QNetworkReply*)),
|
connect(page, SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||||
SLOT(unsupportedContent(QNetworkReply*)));
|
SLOT(unsupportedContent(QNetworkReply*)));
|
||||||
connect(page, SIGNAL(downloadRequested(const QNetworkRequest&)),
|
connect(page, SIGNAL(downloadRequested(const QNetworkRequest&)),
|
||||||
SLOT(downloadRequested(const QNetworkRequest&)));
|
SLOT(downloadRequested(const QNetworkRequest&)));
|
||||||
|
if (setupScript.size()) loadSetup(setupScript);
|
||||||
|
if (scriptFile.size()) loadFile(scriptFile);
|
||||||
}
|
}
|
||||||
virtual ~TestGUI() {}
|
virtual ~TestGUI() {}
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void on__load_clicked() {
|
void on__load_clicked() {
|
||||||
enterText(true);
|
enterText(true);
|
||||||
if (_record->isChecked())
|
if (_record->isChecked())
|
||||||
appendCommand("load "+_url->text());
|
appendCommand("load "+map(_url->text()));
|
||||||
_web->load(_url->text());
|
_web->load(_url->text());
|
||||||
}
|
}
|
||||||
void on__abort_clicked() {
|
void on__abort_clicked() {
|
||||||
@@ -61,28 +69,15 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
void on__actionOpen_triggered() {
|
void on__actionOpen_triggered() {
|
||||||
QString name(QFileDialog::getOpenFileName(this, tr("Open Test Script")));
|
QString name(QFileDialog::getOpenFileName(this, tr("Open Test Script")));
|
||||||
if (name.isEmpty()) return;
|
if (name.isEmpty()) return;
|
||||||
on__actionRevertToSaved_triggered(name);
|
loadFile(name);
|
||||||
|
}
|
||||||
|
void on__actionOpenSetupScript_triggered() {
|
||||||
|
QString name(QFileDialog::getOpenFileName(this, tr("Open Setup Script")));
|
||||||
|
if (name.isEmpty()) return;
|
||||||
|
loadSetup(name);
|
||||||
}
|
}
|
||||||
void on__actionRevertToSaved_triggered() {
|
void on__actionRevertToSaved_triggered() {
|
||||||
on__actionRevertToSaved_triggered(_filename);
|
loadFile(_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() {
|
void on__actionSaveAs_triggered() {
|
||||||
QString name(QFileDialog::getSaveFileName(this, tr("Save Test Script")));
|
QString name(QFileDialog::getSaveFileName(this, tr("Save Test Script")));
|
||||||
@@ -120,18 +115,24 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
bool oldRecordState(_record->isChecked());
|
bool oldRecordState(_record->isChecked());
|
||||||
_run->setEnabled(false);
|
_run->setEnabled(false);
|
||||||
try {
|
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;
|
Script script;
|
||||||
connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString)));
|
connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString)));
|
||||||
|
xml::Node testsuite("testsuite");
|
||||||
|
if (_setupscriptactive->isEnabled()
|
||||||
|
&& _setupscriptactive->isChecked()) {
|
||||||
|
script.parse(_setupscript->toPlainText().split('\n'));
|
||||||
|
testsuite.attr("name") = "setup-script";
|
||||||
|
testsuite.attr("timestamp") =
|
||||||
|
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
|
||||||
|
script.run(_web->page()->mainFrame(), testsuite, QString(), false);
|
||||||
|
script.reset();
|
||||||
|
}
|
||||||
|
QString text(_testscript->textCursor().selection().toPlainText());
|
||||||
|
if (text.isEmpty()) text = _testscript->toPlainText();
|
||||||
script.parse(text.split('\n'));
|
script.parse(text.split('\n'));
|
||||||
|
testsuite.attr("name") = "setup-script";
|
||||||
|
testsuite.attr("timestamp") =
|
||||||
|
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
|
||||||
script.run(_web->page()->mainFrame(), testsuite, QString(), false);
|
script.run(_web->page()->mainFrame(), testsuite, QString(), false);
|
||||||
} catch (std::exception &x) {
|
} catch (std::exception &x) {
|
||||||
QMessageBox::critical(this, tr("Script Failed"),
|
QMessageBox::critical(this, tr("Script Failed"),
|
||||||
@@ -153,28 +154,14 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
highlight(_web->page()->mainFrame()->documentElement()
|
highlight(_web->page()->mainFrame()->documentElement()
|
||||||
.findFirst(_selector->text()));
|
.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() {
|
void on__jsExecute_clicked() {
|
||||||
enterText(true);
|
enterText(true);
|
||||||
execute(selector(), _javascriptCode->toPlainText());
|
_jsResult->setText(execute(selector(), _javascriptCode->toPlainText()));
|
||||||
}
|
}
|
||||||
void on__web_linkClicked(const QUrl& url) {
|
void on__web_linkClicked(const QUrl& url) {
|
||||||
enterText(true);
|
enterText(true);
|
||||||
if (_record->isChecked())
|
if (_record->isChecked())
|
||||||
appendCommand("load "+url.url());
|
appendCommand("load "+map(url.url()));
|
||||||
}
|
}
|
||||||
void on__web_loadProgress(int progress) {
|
void on__web_loadProgress(int progress) {
|
||||||
enterText(true);
|
enterText(true);
|
||||||
@@ -183,7 +170,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
void on__web_loadStarted() {
|
void on__web_loadStarted() {
|
||||||
enterText(true);
|
enterText(true);
|
||||||
if (_record->isChecked())
|
if (_record->isChecked())
|
||||||
appendCommand("expect loadStarted");
|
appendCommand("expect "+map("loadStarted"));
|
||||||
_progress->setValue(0);
|
_progress->setValue(0);
|
||||||
_urlStack->setCurrentIndex(PROGRESS_VIEW);
|
_urlStack->setCurrentIndex(PROGRESS_VIEW);
|
||||||
}
|
}
|
||||||
@@ -196,7 +183,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
void on__web_urlChanged(const QUrl& url) {
|
void on__web_urlChanged(const QUrl& url) {
|
||||||
enterText(true);
|
enterText(true);
|
||||||
if (_record->isChecked())
|
if (_record->isChecked())
|
||||||
appendCommand("expect urlChanged "+url.url());
|
appendCommand("expect "+map("urlChanged "+url.url()));
|
||||||
}
|
}
|
||||||
void on__web_selectionChanged() {
|
void on__web_selectionChanged() {
|
||||||
_source->setPlainText(_web->hasSelection()
|
_source->setPlainText(_web->hasSelection()
|
||||||
@@ -205,15 +192,55 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
}
|
}
|
||||||
void on__web_loadFinished(bool ok) {
|
void on__web_loadFinished(bool ok) {
|
||||||
enterText(true);
|
enterText(true);
|
||||||
if (_record->isChecked())
|
if (_record->isChecked()) {
|
||||||
appendCommand("expect loadFinished "
|
QString text(_testscript->toPlainText());
|
||||||
+QString(ok?"true":"false"));
|
QStringList lines(text.split("\n"));
|
||||||
|
if (ok && lines.size()>1 &&
|
||||||
|
lines.last().startsWith("expect urlChanged") &&
|
||||||
|
lines.at(lines.size()-2)=="expect loadStarted") {
|
||||||
|
// replace three expect lines by one single line
|
||||||
|
QString url(lines.last().replace("expect urlChanged", "").trimmed());
|
||||||
|
lines.removeLast(); lines.removeLast();
|
||||||
|
_testscript->setPlainText(lines.join("\n"));
|
||||||
|
_testscript->moveCursor(QTextCursor::End);
|
||||||
|
_testscript->ensureCursorVisible();
|
||||||
|
appendCommand("expect "+map("load "+url));
|
||||||
|
} else {
|
||||||
|
appendCommand("expect "+map("loadFinished "
|
||||||
|
+QString(ok?"true":"false")));
|
||||||
|
}
|
||||||
|
}
|
||||||
_urlStack->setCurrentIndex(URL_VIEW);
|
_urlStack->setCurrentIndex(URL_VIEW);
|
||||||
on__web_selectionChanged();
|
on__web_selectionChanged();
|
||||||
setLinks();
|
setLinks();
|
||||||
setForms();
|
setForms();
|
||||||
setDom();
|
setDom();
|
||||||
}
|
}
|
||||||
|
void on__setupscript_textChanged() {
|
||||||
|
bool oldRecordState(_record->isChecked());
|
||||||
|
_run->setEnabled(false);
|
||||||
|
_setupscriptactive->setEnabled(false);
|
||||||
|
try {
|
||||||
|
_setupscriptstatus->setText(trUtf8("?"));
|
||||||
|
xml::Node testsuite("testsuite");
|
||||||
|
testsuite.attr("name") = "setup-script";
|
||||||
|
testsuite.attr("timestamp") =
|
||||||
|
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
|
||||||
|
Script script;
|
||||||
|
TestWebPage page(0, true);
|
||||||
|
script.parse(_setupscript->toPlainText().split('\n'));
|
||||||
|
script.run(page.mainFrame(), testsuite, QString(), false);
|
||||||
|
_setupScript.cleanup();
|
||||||
|
_setupScript.parse(_setupscript->toPlainText().split('\n'));
|
||||||
|
_setupScript.run(page.mainFrame(), testsuite, QString(), false);;
|
||||||
|
_setupscriptstatus->setText(trUtf8("✔"));
|
||||||
|
_setupscriptactive->setEnabled(true);
|
||||||
|
} catch (std::exception &x) {
|
||||||
|
_setupscriptstatus->setText(trUtf8("✘"));
|
||||||
|
}
|
||||||
|
_run->setEnabled(true);
|
||||||
|
_record->setChecked(oldRecordState);
|
||||||
|
}
|
||||||
void on__forms_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
|
void on__forms_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
_source->setPlainText(item->data(0, Qt::UserRole).toString());
|
_source->setPlainText(item->data(0, Qt::UserRole).toString());
|
||||||
@@ -225,7 +252,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
void uploadFile(QString filename) {
|
void uploadFile(QString filename) {
|
||||||
enterText(true);
|
enterText(true);
|
||||||
if (_record->isChecked())
|
if (_record->isChecked())
|
||||||
appendCommand("upload "+filename);
|
appendCommand("upload "+map(filename));
|
||||||
}
|
}
|
||||||
void unsupportedContent(QNetworkReply* reply) {
|
void unsupportedContent(QNetworkReply* reply) {
|
||||||
if (!_record->isChecked()) return;
|
if (!_record->isChecked()) return;
|
||||||
@@ -241,7 +268,8 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
QString text(_testscript->toPlainText());
|
QString text(_testscript->toPlainText());
|
||||||
int pos1(text.lastIndexOf(QRegularExpression("^do ")));
|
int pos1(text.lastIndexOf(QRegularExpression("^do ")));
|
||||||
int pos2(text.lastIndexOf(QRegularExpression("^load ")));
|
int pos2(text.lastIndexOf(QRegularExpression("^load ")));
|
||||||
text.insert(pos1>pos2?pos1:pos2, "download "+filename);
|
int pos3(text.lastIndexOf(QRegularExpression("^click ")));
|
||||||
|
text.insert(mrw::max(pos1, pos2, pos3), "download "+filename);
|
||||||
_testscript->setPlainText(text);
|
_testscript->setPlainText(text);
|
||||||
_testscript->moveCursor(QTextCursor::End);
|
_testscript->moveCursor(QTextCursor::End);
|
||||||
_testscript->ensureCursorVisible();
|
_testscript->ensureCursorVisible();
|
||||||
@@ -259,6 +287,8 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
void appendCommand(const QString& txt) {
|
void appendCommand(const QString& txt) {
|
||||||
_testscript->appendPlainText(txt);
|
_testscript->appendPlainText(txt);
|
||||||
QScrollBar *vb(_testscript->verticalScrollBar());
|
QScrollBar *vb(_testscript->verticalScrollBar());
|
||||||
|
_testscript->moveCursor(QTextCursor::End);
|
||||||
|
_testscript->ensureCursorVisible();
|
||||||
if (!vb) return;
|
if (!vb) return;
|
||||||
vb->setValue(vb->maximum());
|
vb->setValue(vb->maximum());
|
||||||
}
|
}
|
||||||
@@ -315,16 +345,31 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
if (mooCombo.hasMatch()) {
|
if (mooCombo.hasMatch()) {
|
||||||
// special treatment for moo tools combobox (e.g. used
|
// special treatment for moo tools combobox (e.g. used
|
||||||
// in joomla)
|
// in joomla)
|
||||||
appendCommand("click "+mooCombo.captured(1)+">a");
|
appendCommand("click "+map(mooCombo.captured(1)+">a"));
|
||||||
appendCommand("sleep 1");
|
appendCommand("sleep "+map("1"));
|
||||||
} else if (mooComboItem.hasMatch()) {
|
} else if (mooComboItem.hasMatch()) {
|
||||||
// special treatment for item in moo tools combobox
|
// special treatment for item in moo tools combobox
|
||||||
appendCommand
|
appendCommand
|
||||||
("click li.active-result[data-option-array-index=\""
|
("click "+map("li.active-result[data-option-array-index=\""
|
||||||
+element.attribute("data-option-array-index")+"\"]");
|
+element.attribute("data-option-array-index")
|
||||||
appendCommand("sleep 1");
|
+"\"]"));
|
||||||
|
appendCommand("sleep "+map("1"));
|
||||||
|
} else if (_lastFocused.tagName()=="SELECT") {
|
||||||
|
// click on a select results in a value change
|
||||||
|
// find all selected options ...
|
||||||
|
QStringList v;
|
||||||
|
Q_FOREACH(QWebElement option,
|
||||||
|
_lastFocused.findAll("option")) {
|
||||||
|
//! @bug QT does not support selected
|
||||||
|
if (option.evaluateJavaScript("this.selected").toBool())
|
||||||
|
v += value(option);
|
||||||
|
}
|
||||||
|
setValue(selected, v);
|
||||||
} else {
|
} else {
|
||||||
appendCommand("click "+selected);
|
appendCommand("click "+map(selected));
|
||||||
|
}
|
||||||
|
if (_lastFocused.tagName()=="INPUT") {
|
||||||
|
_typing = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
appendCommand("# click, but where?");
|
appendCommand("# click, but where?");
|
||||||
@@ -332,13 +377,11 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case QEvent::MouseButtonPress: {
|
case QEvent::MouseButtonPress: {
|
||||||
enterText(true);
|
|
||||||
} break;
|
} break;
|
||||||
case QEvent::ChildRemoved: { // select option value changed
|
case QEvent::ChildRemoved: { // select option value changed
|
||||||
enterText(true);
|
enterText(true);
|
||||||
_typing = true;
|
_typing = true;
|
||||||
_lastFocused=element;
|
_lastFocused=element;
|
||||||
_keyStrokes = "dummy";
|
|
||||||
} break;
|
} break;
|
||||||
case QEvent::InputMethodQuery:
|
case QEvent::InputMethodQuery:
|
||||||
case QEvent::ToolTipChange:
|
case QEvent::ToolTipChange:
|
||||||
@@ -353,12 +396,44 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
|
void loadFile(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 loadSetup(QString name) {
|
||||||
|
QFile file(name);
|
||||||
|
try {
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
throw std::runtime_error("file open failed");
|
||||||
|
_setupscript->setPlainText(QString::fromUtf8(file.readAll()));
|
||||||
|
if (file.error()!=QFileDevice::NoError)
|
||||||
|
throw std::runtime_error("file read failed");
|
||||||
|
on__setupscript_textChanged();
|
||||||
|
} 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 enterText(bool force=false) {
|
void enterText(bool force=false) {
|
||||||
if (!force && (!_typing || _lastFocused==focused())) return;
|
if (!force && (!_typing || _lastFocused==focused())) return;
|
||||||
if (_keyStrokes.size() && !_lastFocused.isNull()) {
|
if (_typing && !_lastFocused.isNull())
|
||||||
store(selector(_lastFocused), "this.value='"
|
setValue(selector(_lastFocused), value(_lastFocused));
|
||||||
+value(_lastFocused).replace("\n", "\\n")+"';");
|
|
||||||
}
|
|
||||||
_lastFocused = QWebElement();
|
_lastFocused = QWebElement();
|
||||||
_keyStrokes.clear();
|
_keyStrokes.clear();
|
||||||
_typing = false;
|
_typing = false;
|
||||||
@@ -451,15 +526,54 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
// else
|
// else
|
||||||
// return element.toPlainText();
|
// return element.toPlainText();
|
||||||
}
|
}
|
||||||
void store(const QString& selector, QString code) {
|
QString map(QString in) {
|
||||||
if (_record->isChecked())
|
if (_setupscriptactive->isEnabled()
|
||||||
appendCommand("do "+selector+"\n "
|
&& _setupscriptactive->isChecked()) {
|
||||||
+code.replace("\n", "\\n"));
|
return _setupScript.insertvars(in);
|
||||||
}
|
}
|
||||||
void execute(const QString& selector, const QString& code) {
|
return in;
|
||||||
store(selector, code);
|
}
|
||||||
_web->page()->mainFrame()->documentElement().findFirst(selector)
|
void javascript(const QString& selector, QString code) {
|
||||||
.evaluateJavaScript(code);
|
if (_record->isChecked())
|
||||||
|
appendCommand("do "+map(selector)+"\n "
|
||||||
|
+map(code).replace("\n", "\\n"));
|
||||||
|
}
|
||||||
|
void cleanup(const QString& selector) {
|
||||||
|
QString text(_testscript->toPlainText());
|
||||||
|
QStringList lines(text.split("\n"));
|
||||||
|
bool changed(false);
|
||||||
|
while (lines.size() &&
|
||||||
|
(lines.last()=="click "+selector ||
|
||||||
|
lines.last().startsWith("setvalue "+selector+" -> "))) {
|
||||||
|
lines.removeLast();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
_testscript->setPlainText(lines.join("\n"));
|
||||||
|
_testscript->moveCursor(QTextCursor::End);
|
||||||
|
_testscript->ensureCursorVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void setValue(const QString& selector, QString code) {
|
||||||
|
if (_record->isChecked()) {
|
||||||
|
cleanup(selector);
|
||||||
|
appendCommand("setvalue "+map(selector)+" -> '"
|
||||||
|
+map(code).replace("'", "\\'").replace("\n", "\\n")+"'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void setValue(const QString& selector, QStringList code) {
|
||||||
|
if (_record->isChecked()) {
|
||||||
|
cleanup(selector);
|
||||||
|
appendCommand("setvalue "+map(selector)+" -> '"+
|
||||||
|
map(code.replaceInStrings("'", "\\'")
|
||||||
|
.replaceInStrings("\n", "\\n")
|
||||||
|
.join("', '")+"'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString execute(const QString& selector, const QString& code) {
|
||||||
|
javascript(selector, code);
|
||||||
|
return _web->page()->mainFrame()->documentElement().findFirst(selector)
|
||||||
|
.evaluateJavaScript(code).toString();
|
||||||
}
|
}
|
||||||
void setLinks() {
|
void setLinks() {
|
||||||
QWebElementCollection links(_web->page()->mainFrame()->documentElement()
|
QWebElementCollection links(_web->page()->mainFrame()->documentElement()
|
||||||
@@ -675,6 +789,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
|
|||||||
QString _keyStrokes; // collect key strokes
|
QString _keyStrokes; // collect key strokes
|
||||||
bool _typing; // user is typing
|
bool _typing; // user is typing
|
||||||
bool _inEventFilter; // actually handling event filter
|
bool _inEventFilter; // actually handling event filter
|
||||||
|
Script _setupScript;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TESTGUI_HXX
|
#endif // TESTGUI_HXX
|
||||||
|
122
src/testgui.ui
122
src/testgui.ui
@@ -91,7 +91,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>888</width>
|
<width>888</width>
|
||||||
<height>23</height>
|
<height>22</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuViews">
|
<widget class="QMenu" name="menuViews">
|
||||||
@@ -111,6 +111,7 @@
|
|||||||
<string>File</string>
|
<string>File</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="_actionOpen"/>
|
<addaction name="_actionOpen"/>
|
||||||
|
<addaction name="_actionOpenSetupScript"/>
|
||||||
<addaction name="_actionSave"/>
|
<addaction name="_actionSave"/>
|
||||||
<addaction name="_actionSaveAs"/>
|
<addaction name="_actionSaveAs"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
@@ -121,8 +122,15 @@
|
|||||||
<addaction name="_actionClear"/>
|
<addaction name="_actionClear"/>
|
||||||
<addaction name="_actionQuit"/>
|
<addaction name="_actionQuit"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuHelp">
|
||||||
|
<property name="title">
|
||||||
|
<string>Help</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="_actionCommands"/>
|
||||||
|
</widget>
|
||||||
<addaction name="menuFile"/>
|
<addaction name="menuFile"/>
|
||||||
<addaction name="menuViews"/>
|
<addaction name="menuViews"/>
|
||||||
|
<addaction name="menuHelp"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QStatusBar" name="statusbar"/>
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
<widget class="QDockWidget" name="_domDock">
|
<widget class="QDockWidget" name="_domDock">
|
||||||
@@ -241,31 +249,14 @@
|
|||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="_jsClick">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Click</string>
|
<string>Result:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="_jsValue">
|
<widget class="QLineEdit" name="_jsResult"/>
|
||||||
<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>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
@@ -397,7 +388,6 @@ this.dispatchEvent(evObj);</string>
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
<zorder></zorder>
|
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QDockWidget" name="_scriptDock">
|
<widget class="QDockWidget" name="_scriptDock">
|
||||||
@@ -463,6 +453,84 @@ this.dispatchEvent(evObj);</string>
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QDockWidget" name="dockWidget">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Setup Script</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="dockWidgetArea">
|
||||||
|
<number>4</number>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="dockWidgetContents">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="_setupscriptactive">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>active</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Status:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="_setupscriptstatus">
|
||||||
|
<property name="text">
|
||||||
|
<string>?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="_setupscript"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QDockWidget" name="dockWidget_2">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Script Commands</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="dockWidgetArea">
|
||||||
|
<number>4</number>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="dockWidgetContents_3">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_7">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QTextBrowser" name="_commands">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
<action name="_actionDOMTree">
|
<action name="_actionDOMTree">
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -583,6 +651,16 @@ this.dispatchEvent(evObj);</string>
|
|||||||
<string>Revert to saved</string>
|
<string>Revert to saved</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="_actionOpenSetupScript">
|
||||||
|
<property name="text">
|
||||||
|
<string>Open Setup Script ...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="_actionCommands">
|
||||||
|
<property name="text">
|
||||||
|
<string>Commands ...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
#include <xml-cxx/xml.hxx>
|
#include <xml-cxx/xml.hxx>
|
||||||
#include <mrw/string.hxx>
|
#include <mrw/string.hxx>
|
||||||
|
|
||||||
std::string VERSION("0.9.4");
|
std::string VERSION("0.9.5");
|
||||||
|
|
||||||
QString format(QString txt, int indent = 2, int cpl = 60) {
|
QString format(QString txt, int indent = 2, int cpl = 60) {
|
||||||
QStringList res;
|
QStringList res;
|
||||||
|
@@ -6,9 +6,14 @@ int main(int argc, char *argv[]) try {
|
|||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
|
parser.addOption(QCommandLineOption
|
||||||
|
(QStringList()<<"u"<<"url",
|
||||||
|
"set initial URL to <url>", "url"));
|
||||||
parser.process(a);
|
parser.process(a);
|
||||||
QStringList urls(parser.positionalArguments());
|
QStringList scripts(parser.positionalArguments());
|
||||||
TestGUI w(0, urls.size()?urls[0]:"");
|
TestGUI w(0, parser.value("url"),
|
||||||
|
scripts.size()>1?scripts[0]:"",
|
||||||
|
scripts.size()>1?scripts[1]:scripts.size()?scripts[0]:"");
|
||||||
w.show();
|
w.show();
|
||||||
return a.exec();
|
return a.exec();
|
||||||
} catch (std::exception &x) {
|
} catch (std::exception &x) {
|
||||||
|
Reference in New Issue
Block a user