diff --git a/configure.ac b/configure.ac index 620efd9..99b3013 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,8 @@ if test -z "${QMAKE}"; then AC_MSG_ERROR([cannot find a qmake command]) fi +# requires mrw-c++ - to be defined ... + README=$(tail -n +3 README) README_DEB=$(tail -n +3 README | sed -e 's/^$/./g' -e 's/^/ /g') DESCRIPTION=$(head -1 README) diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..36516cb --- /dev/null +++ b/docker/Dockerfile @@ -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 diff --git a/docker/runtests.sh b/docker/runtests.sh new file mode 100755 index 0000000..a027708 --- /dev/null +++ b/docker/runtests.sh @@ -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} diff --git a/src/commands.hxx b/src/commands.hxx index f685d0a..35ebb29 100644 --- a/src/commands.hxx +++ b/src/commands.hxx @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -35,8 +36,8 @@ class Command; class Logger { public: Logger(Command* command, Script* script); - void plainlog(QString txt); - void log(QString txt); + void operator[](QString txt); + void operator()(QString txt); ~Logger(); private: Command* _command; @@ -170,7 +171,7 @@ class Empty: public Command { "Empty lines are allowed"; } QString command() const { - return ""; + return tag(); } std::shared_ptr parse(Script*, QString, QStringList&, int) { std::shared_ptr cmd(new Empty()); @@ -258,14 +259,14 @@ class Screenshot: public Command { } QString description() const { return - "screenshot " + tag()+" " "\n\n" "Create a PNG screenshot of the actual web page and store it in the " "file .png. If not already opened, a browser window " "will pop up to take the screenshot."; } QString command() const { - return "screenshot "+_filename; + return tag()+" "+_filename; } std::shared_ptr parse(Script*, QString args, QStringList&, int) { std::shared_ptr cmd(new Screenshot()); @@ -325,6 +326,25 @@ class Script: public QObject { void logging(QString); public: typedef std::pair 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()second->description(); + switch (f) { + case PLAIN: { + cmds+="\n\n\nCOMMAND: "+it->first+"\n\n"+it->second->description(); + } break; + case HTML: { + cmds+="

"+it->first+"

" + +it->second->description() + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace(QRegularExpression("<([^ ]+)>"), + "\\1") + .replace(QRegularExpression("(\n[^ ][^\n]*)(\n +-)"), + "\\1

    \\2") + .replace(QRegularExpression("(\n +-[^\n]*\n)([^ ])"), + "\\1
\\2") + .replace(QRegularExpression("\n +- ([^\n]*)()?"), + "
  • \\1
  • \\2") + .replace("\n", "") + .replace("\n ", "\n  ") + .replace("\n\n", "

    ") + .replace("\n", "
    ") + +"

    "; + } break; + } return cmds.trimmed(); } void reset() { @@ -368,6 +412,14 @@ class Script: public QObject { _ignores.clear(); _cout.clear(); _cerr.clear(); + _ignoreSignalsUntil.clear(); + } + void cleanup() { + reset(); + _variables.clear(); + _rvariables.clear(); + _timeout = 20; + _clicktype = JAVASCRIPT_CLICK; } std::shared_ptr parse(QStringList& in, int linenr) try { QString line(in.takeFirst().trimmed()); @@ -555,19 +607,36 @@ class Script: public QObject { } void set(QString name, QString value) { _variables[name] = value; + _rvariables[value] = name; } void unset(QString name) { + _rvariables.remove(_variables[name]); _variables.remove(name); } void timeout(int t) { _timeout = t; } + void clicktype(ClickType c) { + _clicktype = c; + } + ClickType clicktype() { + return _clicktype; + } QString replacevars(QString txt) { for(QMap::iterator it(_variables.begin()); it!=_variables.end(); ++it) txt.replace(it.key(), it.value()); return txt; } + QString insertvars(QString txt) { + QMapIterator it(_rvariables); + it.toBack(); + while (it.hasPrevious()) { + it.previous(); + 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; @@ -703,8 +772,10 @@ class Script: public QObject { QString _cerr; bool _screenshots; QString _ignoreSignalsUntil; - QMap _variables; + QMap _variables; ///< variable mapping + QMap _rvariables; ///< reverse variable mapping int _timeout; + ClickType _clicktype; }; class Do: public Command { @@ -714,7 +785,7 @@ class Do: public Command { } QString description() const { return - "do \n \n " + tag()+" \n \n " "\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 " @@ -723,7 +794,7 @@ class Do: public Command { "one space"; } QString command() const { - return "do "+_selector+_javascript; + return tag()+" "+_selector+_javascript; } std::shared_ptr parse(Script*, QString args, QStringList& in, int) { @@ -735,8 +806,9 @@ class Do: public Command { } bool execute(Script* script, QWebFrame* frame) { Logger log(this, script); - QWebElement element(find(frame, _selector)); - if (element.isNull()) throw ElementNotFound(_selector); + QWebElement element(find(frame, script->replacevars(_selector))); + if (element.isNull()) + throw ElementNotFound(script->replacevars(_selector)); _result = element.evaluateJavaScript(script->replacevars(_javascript)).toString(); return true; @@ -753,12 +825,12 @@ class Load: public Command { } QString description() const { return - "load " + tag()+" " "\n\n" "Load an URL, the URL is given as parameter in full syntax."; } QString command() const { - return "load "+_url; + return tag()+" "+_url; } std::shared_ptr parse(Script*, QString args, QStringList&, int) { std::shared_ptr cmd(new Load()); @@ -781,7 +853,7 @@ class Expect: public Command { } QString description() const { return - "expect []" + tag()+" []" "\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 " @@ -800,7 +872,7 @@ class Expect: public Command { " - loadFinished true"; } QString command() const { - return "expect "+_signal._signal + return tag()+" "+_signal._signal +(_signal._args.size()?" "+_signal._args.join(' '):QString()); } std::shared_ptr parse(Script*, QString args, QStringList&, int) { @@ -851,12 +923,12 @@ class Open: public Command { } QString description() const { return - "open" + tag()+ "\n\n" "Open the browser window, so you can follow the test steps visually."; } QString command() const { - return "open"; + return tag(); } std::shared_ptr parse(Script*, QString, QStringList&, int) { std::shared_ptr cmd(new Open()); @@ -876,14 +948,14 @@ class Sleep: public Command { } QString description() const { return - "sleep " + tag()+" " "\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; + return tag()+" "+_time; } std::shared_ptr parse(Script*, QString time, QStringList&, int) { std::shared_ptr cmd(new Sleep()); @@ -913,14 +985,14 @@ class Exit: public Command { } QString description() const { return - "exit" + tag()+ "\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"; + return tag(); } std::shared_ptr parse(Script*, QString, QStringList&, int) { std::shared_ptr cmd(new Exit()); @@ -939,7 +1011,7 @@ class IgnoreTo: public Command { } QString description() const { return - "ignoreto