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(find(frame, script->replacevars(_selector)));
|
QWebElement element(frame->documentElement());
|
||||||
if (element.isNull())
|
if (_selector.size()) {
|
||||||
throw ElementNotFound(script->replacevars(_selector));
|
QWebElement element(find(frame, script->replacevars(_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;
|
||||||
@@ -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