click has an optional clicktype; do has an optional selector; better detection and handling of moo-tool (joomla3) elements
This commit is contained in:
		@@ -30,6 +30,45 @@
 | 
				
			|||||||
#include <cassert>
 | 
					#include <cassert>
 | 
				
			||||||
#include <xml-cxx/xml.hxx>
 | 
					#include <xml-cxx/xml.hxx>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace std {
 | 
				
			||||||
 | 
					  template<typename T> class optional {
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					      T* _opt;
 | 
				
			||||||
 | 
					      bool _set;
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					      optional():
 | 
				
			||||||
 | 
					          _opt(0), _set(false) {
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      optional(const T& other):
 | 
				
			||||||
 | 
					          _opt(new T(other)), _set(true) {
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ~optional() {
 | 
				
			||||||
 | 
					        if (_set) delete _opt;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      optional& operator=(const T& other) {
 | 
				
			||||||
 | 
					        if (_set) delete _opt;
 | 
				
			||||||
 | 
					        _set = true;
 | 
				
			||||||
 | 
					        _opt = new T(other);
 | 
				
			||||||
 | 
					        return *this;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      T* operator->() {
 | 
				
			||||||
 | 
					        return _opt;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      T& operator*() {
 | 
				
			||||||
 | 
					        return *_opt;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const T* operator->() const {
 | 
				
			||||||
 | 
					        return _opt;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const T& operator*() const {
 | 
				
			||||||
 | 
					        return *_opt;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      operator bool() const {
 | 
				
			||||||
 | 
					        return _set;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Script;
 | 
					class Script;
 | 
				
			||||||
class Command;
 | 
					class Command;
 | 
				
			||||||
class Function;
 | 
					class Function;
 | 
				
			||||||
@@ -878,7 +917,7 @@ class Do: public Command {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    QString description() const {
 | 
					    QString description() const {
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
        tag()+" <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 "
 | 
				
			||||||
@@ -892,15 +931,18 @@ class Do: public Command {
 | 
				
			|||||||
    std::shared_ptr<Command> parse(Script*, QString args,
 | 
					    std::shared_ptr<Command> parse(Script*, QString args,
 | 
				
			||||||
                                   QStringList& in, int) {
 | 
					                                   QStringList& in, int) {
 | 
				
			||||||
      std::shared_ptr<Do> cmd(new Do());
 | 
					      std::shared_ptr<Do> cmd(new Do());
 | 
				
			||||||
      cmd->_selector = args;
 | 
					      cmd->_selector = args.trimmed();
 | 
				
			||||||
      cmd->_javascript = subCommandBlock(in).join("\n");
 | 
					      cmd->_javascript = subCommandBlock(in).join("\n");
 | 
				
			||||||
      return cmd;
 | 
					      return cmd;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    bool execute(Script* script, QWebFrame* frame) {
 | 
					    bool execute(Script* script, QWebFrame* frame) {
 | 
				
			||||||
      Logger log(this, script);
 | 
					      Logger log(this, script);
 | 
				
			||||||
 | 
					      QWebElement element(frame->documentElement());
 | 
				
			||||||
 | 
					      if (_selector.size()) {
 | 
				
			||||||
        QWebElement element(find(frame, script->replacevars(_selector)));
 | 
					        QWebElement element(find(frame, script->replacevars(_selector)));
 | 
				
			||||||
        if (element.isNull())
 | 
					        if (element.isNull())
 | 
				
			||||||
          throw ElementNotFound(script->replacevars(_selector));
 | 
					          throw ElementNotFound(script->replacevars(_selector));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      _result =
 | 
					      _result =
 | 
				
			||||||
        element.evaluateJavaScript(script->replacevars(_javascript)).toString();
 | 
					        element.evaluateJavaScript(script->replacevars(_javascript)).toString();
 | 
				
			||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
@@ -1454,32 +1496,43 @@ class Click: public Command {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    QString description() const {
 | 
					    QString description() const {
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
        tag()+" <selector>"
 | 
					        tag()+" [<clicktype>] <selector>"
 | 
				
			||||||
        "\n\n"
 | 
					        "\n\n"
 | 
				
			||||||
        "Click on the specified element";
 | 
					        "Click on the specified element. Either you explicitely specify a click"
 | 
				
			||||||
 | 
					        " type, such as <realmouse> or <javascript>, or the previously set or"
 | 
				
			||||||
 | 
					        " the default clicktype is used.";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    QString command() const {
 | 
					    QString command() const {
 | 
				
			||||||
      return tag()+" "+_selector;
 | 
					      return tag()+(_clicktype
 | 
				
			||||||
 | 
					                    ?(*_clicktype==Script::REAL_MOUSE_CLICK?" realmouse"
 | 
				
			||||||
 | 
					                      :*_clicktype==Script::JAVASCRIPT_CLICK?" javascript":"")
 | 
				
			||||||
 | 
					                    :"")+" "+_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());
 | 
				
			||||||
      cmd->_selector = args;
 | 
					      if (args.trimmed().contains(QRegularExpression("^realmouse ")))
 | 
				
			||||||
 | 
					        cmd->_clicktype = Script::REAL_MOUSE_CLICK;
 | 
				
			||||||
 | 
					      else if (args.trimmed().contains(QRegularExpression("^javascript ")))
 | 
				
			||||||
 | 
					        cmd->_clicktype = Script::JAVASCRIPT_CLICK;
 | 
				
			||||||
 | 
					      cmd->_selector =
 | 
				
			||||||
 | 
					        args.remove(QRegularExpression("^ *(realmouse|javascript) +"));
 | 
				
			||||||
      return cmd;
 | 
					      return cmd;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    bool execute(Script* script, QWebFrame* frame) {
 | 
					    bool execute(Script* script, QWebFrame* frame) {
 | 
				
			||||||
      Logger log(this, script);
 | 
					      Logger log(this, script);
 | 
				
			||||||
      switch (script->clicktype()) {
 | 
					      QString clicktarget(script->replacevars(_selector));
 | 
				
			||||||
 | 
					      switch (_clicktype ? *_clicktype : script->clicktype()) {
 | 
				
			||||||
        case Script::REAL_MOUSE_CLICK: {
 | 
					        case Script::REAL_MOUSE_CLICK: {
 | 
				
			||||||
          log("Script::REAL_MOUSE_CLICK");
 | 
					          log("Script::REAL_MOUSE_CLICK");
 | 
				
			||||||
          realMouseClick(frame, script->replacevars(_selector));
 | 
					          realMouseClick(frame, clicktarget);
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case Script::JAVASCRIPT_CLICK:
 | 
					        case Script::JAVASCRIPT_CLICK:
 | 
				
			||||||
        default: {
 | 
					        default: {
 | 
				
			||||||
          log("Script::JAVASCRIPT_CLICK");
 | 
					          log("Script::JAVASCRIPT_CLICK");
 | 
				
			||||||
          QWebElement element(find(frame, script->replacevars(_selector)));
 | 
					          QWebElement element(find(frame, clicktarget));
 | 
				
			||||||
          if (element.isNull())
 | 
					          if (element.isNull())
 | 
				
			||||||
            throw ElementNotFound(script->replacevars(_selector));
 | 
					            throw ElementNotFound(clicktarget);
 | 
				
			||||||
          _result = element.evaluateJavaScript("this.click();").toString();
 | 
					          _result = element.evaluateJavaScript("this.click();").toString();
 | 
				
			||||||
          break;
 | 
					          break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -1488,6 +1541,7 @@ class Click: public Command {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  private:
 | 
					  private:
 | 
				
			||||||
    QString _selector;
 | 
					    QString _selector;
 | 
				
			||||||
 | 
					    std::optional<Script::ClickType> _clicktype;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Set: public Command {
 | 
					class Set: public Command {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,6 +113,8 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    void on__run_clicked() {
 | 
					    void on__run_clicked() {
 | 
				
			||||||
      bool oldRecordState(_record->isChecked());
 | 
					      bool oldRecordState(_record->isChecked());
 | 
				
			||||||
 | 
					      _record->setChecked(false);
 | 
				
			||||||
 | 
					      _record->setEnabled(false);
 | 
				
			||||||
      _run->setEnabled(false);
 | 
					      _run->setEnabled(false);
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        Script script;
 | 
					        Script script;
 | 
				
			||||||
@@ -134,6 +136,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
 | 
				
			|||||||
                              .arg(x.what()));
 | 
					                              .arg(x.what()));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      _run->setEnabled(true);
 | 
					      _run->setEnabled(true);
 | 
				
			||||||
 | 
					      _record->setEnabled(true);
 | 
				
			||||||
      _record->setChecked(oldRecordState);
 | 
					      _record->setChecked(oldRecordState);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    void on__focused_clicked() {
 | 
					    void on__focused_clicked() {
 | 
				
			||||||
@@ -325,26 +328,9 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
 | 
				
			|||||||
          _lastFocused=element;
 | 
					          _lastFocused=element;
 | 
				
			||||||
          if (_record->isChecked()) {
 | 
					          if (_record->isChecked()) {
 | 
				
			||||||
            if (!element.isNull()) {
 | 
					            if (!element.isNull()) {
 | 
				
			||||||
              QString selected(selector(_lastFocused));
 | 
					              QString selected(selector(element));
 | 
				
			||||||
              QRegularExpressionMatch mooCombo
 | 
					              if (handleMooTools(_lastFocused)) {
 | 
				
			||||||
                (QRegularExpression("^(#jform_[_A-Za-z0-9]+)_chzn>.*$")
 | 
					                // handled in handleMooTools
 | 
				
			||||||
                 .match(selected));
 | 
					 | 
				
			||||||
              QRegularExpressionMatch mooComboItem
 | 
					 | 
				
			||||||
                (QRegularExpression
 | 
					 | 
				
			||||||
                 ("^li\\.highlighted(\\.result-selected)?\\.active-result$")
 | 
					 | 
				
			||||||
                 .match(selected));
 | 
					 | 
				
			||||||
              if (mooCombo.hasMatch()) {
 | 
					 | 
				
			||||||
                // special treatment for moo tools combobox (e.g. used
 | 
					 | 
				
			||||||
                // in joomla)
 | 
					 | 
				
			||||||
                appendCommand("click "+map(mooCombo.captured(1)+">a"));
 | 
					 | 
				
			||||||
                appendCommand("sleep "+map("1"));
 | 
					 | 
				
			||||||
              } else if (mooComboItem.hasMatch()) {
 | 
					 | 
				
			||||||
                // special treatment for item in moo tools combobox
 | 
					 | 
				
			||||||
                appendCommand
 | 
					 | 
				
			||||||
                  ("click "+map("li.active-result[data-option-array-index=\""
 | 
					 | 
				
			||||||
                                +element.attribute("data-option-array-index")
 | 
					 | 
				
			||||||
                                +"\"]"));
 | 
					 | 
				
			||||||
                appendCommand("sleep "+map("1"));
 | 
					 | 
				
			||||||
              } else if (_lastFocused.tagName()=="SELECT") {
 | 
					              } else if (_lastFocused.tagName()=="SELECT") {
 | 
				
			||||||
                // click on a select results in a value change
 | 
					                // click on a select results in a value change
 | 
				
			||||||
                // find all selected options ...
 | 
					                // find all selected options ...
 | 
				
			||||||
@@ -356,15 +342,14 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
 | 
				
			|||||||
                    v += value(option);
 | 
					                    v += value(option);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                setValue(selected, v);
 | 
					                setValue(selected, v);
 | 
				
			||||||
              } else {
 | 
					              } else if (_lastFocused.tagName()=="TEXTAREA" ||
 | 
				
			||||||
                appendCommand("click "+map(selected));
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              if (_lastFocused.tagName()=="TEXTAREA" ||
 | 
					 | 
				
			||||||
                  _lastFocused.tagName()=="INPUT" &&
 | 
					                  _lastFocused.tagName()=="INPUT" &&
 | 
				
			||||||
                  _lastFocused.attribute("type")=="text") {
 | 
					                  _lastFocused.attribute("type")=="text") {
 | 
				
			||||||
                // user clickt in a text edit field, so not the klick
 | 
					                // user clickt in a text edit field, so not the klick
 | 
				
			||||||
                // is important, but the text that will be typed
 | 
					                // is important, but the text that will be typed
 | 
				
			||||||
                _typing = true;
 | 
					                _typing = true;
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                appendCommand("click "+map(selected));
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              appendCommand("# click, but where?");
 | 
					              appendCommand("# click, but where?");
 | 
				
			||||||
@@ -531,7 +516,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
 | 
				
			|||||||
    void javascript(const QString& selector, QString code) {
 | 
					    void javascript(const QString& selector, QString code) {
 | 
				
			||||||
      if (_record->isChecked())
 | 
					      if (_record->isChecked())
 | 
				
			||||||
        appendCommand("do "+map(selector)+"\n  "
 | 
					        appendCommand("do "+map(selector)+"\n  "
 | 
				
			||||||
                      +map(code).replace("\n", "\\n"));
 | 
					                      +map(code).replace("\n", "\n  "));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    void cleanup(const QString& selector) {
 | 
					    void cleanup(const QString& selector) {
 | 
				
			||||||
      QString text(_testscript->toPlainText());
 | 
					      QString text(_testscript->toPlainText());
 | 
				
			||||||
@@ -565,6 +550,53 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
 | 
				
			|||||||
                          .join("', '")+"'"));
 | 
					                          .join("', '")+"'"));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    bool handleMooTools(QWebElement element) {
 | 
				
			||||||
 | 
					      QString selected(selector(element));
 | 
				
			||||||
 | 
					      QRegularExpressionMatch mooCombo
 | 
				
			||||||
 | 
					        (QRegularExpression("^(#jform_[_A-Za-z0-9]+)_chzn>.*$")
 | 
				
			||||||
 | 
					         .match(selected));
 | 
				
			||||||
 | 
					      QRegularExpressionMatch mooComboItem
 | 
				
			||||||
 | 
					        (QRegularExpression
 | 
				
			||||||
 | 
					         ("^li\\.highlighted(\\.result-selected)?\\.active-result$")
 | 
				
			||||||
 | 
					         .match(selected));
 | 
				
			||||||
 | 
					      if (mooCombo.hasMatch()) {
 | 
				
			||||||
 | 
					        // special treatment for moo tools combobox (e.g. used
 | 
				
			||||||
 | 
					        // in joomla)
 | 
				
			||||||
 | 
					        appendCommand("click realmouse "+map(mooCombo.captured(1)+">a"));
 | 
				
			||||||
 | 
					        appendCommand("sleep "+map("1"));
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      } else if (mooComboItem.hasMatch()) {
 | 
				
			||||||
 | 
					        // special treatment for item in moo tools combobox
 | 
				
			||||||
 | 
					        appendCommand
 | 
				
			||||||
 | 
					          ("click realmouse "+map("li.active-result[data-option-array-index=\""
 | 
				
			||||||
 | 
					                                  +element.attribute("data-option-array-index")
 | 
				
			||||||
 | 
					                                  +"\"]"));
 | 
				
			||||||
 | 
					        appendCommand("sleep "+map("1"));
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      } else if (element.tagName()=="INPUT") {
 | 
				
			||||||
 | 
					        for (QWebElement e(element); !(e=e.parent()).isNull();)
 | 
				
			||||||
 | 
					          if (e.hasClass("chzn-container")) {
 | 
				
			||||||
 | 
					            _lastFocused = e;
 | 
				
			||||||
 | 
					            appendCommand("# moo tools combobox with search field");
 | 
				
			||||||
 | 
					            appendCommand("# first set all elements visible");
 | 
				
			||||||
 | 
					            javascript(map(selector(e)+" ul"),
 | 
				
			||||||
 | 
					                       "this.style.height = \"auto\";\n"
 | 
				
			||||||
 | 
					                       "this.style.maxHeight = \"auto\";\n"
 | 
				
			||||||
 | 
					                       "this.style.overflow = \"visible\"");
 | 
				
			||||||
 | 
					            appendCommand("sleep "+map("1"));
 | 
				
			||||||
 | 
					            appendCommand("# moo tools open combobox");
 | 
				
			||||||
 | 
					            appendCommand("click realmouse "+map(selector(e)));
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					      } else if (element.hasAttribute("id") && // is it a selected option?
 | 
				
			||||||
 | 
					                 element.attribute("id").contains("_chzn_o_")) {
 | 
				
			||||||
 | 
					        appendCommand("# moo tools select item");
 | 
				
			||||||
 | 
					        appendCommand("click realmouse "+map(selector(element)));
 | 
				
			||||||
 | 
					        _lastFocused = element;
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    QString execute(const QString& selector, const QString& code) {
 | 
					    QString execute(const QString& selector, const QString& code) {
 | 
				
			||||||
      javascript(selector, code);
 | 
					      javascript(selector, code);
 | 
				
			||||||
      return _web->page()->mainFrame()->documentElement().findFirst(selector)
 | 
					      return _web->page()->mainFrame()->documentElement().findFirst(selector)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user