diff --git a/configure.ac b/configure.ac
index d20a11c..22eba60 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8,8 +8,9 @@
# change this:
m4_define(x_package_name, webtester) # project's name
-m4_define(x_major, 2) # project's major version
-m4_define(x_minor, 2) # project's minor version
+m4_define(x_major, 3) # project's major version
+m4_define(x_minor, 0) # project's minor version
+m4_define(x_least_diff, 123)
# never edit this block:
m4_include(ax_init_standard_project.m4)
diff --git a/src/commands.hxx b/src/commands.hxx
index ae5ed19..bf03959 100644
--- a/src/commands.hxx
+++ b/src/commands.hxx
@@ -278,7 +278,7 @@ class Empty: public Command {
QString description() const {
return
""
- "\n\n"
+ "\n\n--\n\n"
"Empty lines are allowed";
}
QString command() const {
@@ -306,7 +306,7 @@ class Comment: public Command {
QString description() const {
return
"# comment"
- "\n\n"
+ "\n\n--\n\n"
"Comments are lines that start with #";
}
QString command() const {
@@ -383,7 +383,7 @@ class Screenshot: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\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.";
@@ -450,6 +450,7 @@ class Script: public QObject {
void logging(QString);
void progress(QString, int, int, int);
public:
+ typedef std::map> Prototypes;
typedef std::pair Signal;
enum ClickType {
REAL_MOUSE_CLICK,
@@ -500,57 +501,79 @@ class Script: public QObject {
}
QString syntax() const {
return
- "Script syntax is a text file that consists of list of commands. Each "
+ "Script syntax is a text file that consists of a list of commands. Each "
"command starts at the begin of a new line. Empty lines are allowed. "
"Lines that start with \"#\" are treated as comments."
"\n\n"
"Subcommands are indented. The first indented line defines the level of "
- "indentation. All following lines must be indented by the same level."
+ "indentation. All following lines must be indented at least by the same level."
"\n\n"
"Note: When a selector is required as parameter, then the selector "
"is a CSS selector."
"\n\n"
- "Thanks to the filter script doxygen-webtester.sed, you cab use the "
+ "Thanks to the filter script doxygen-webtester.sed, you can use the "
"comments for producing doxygen documenation. Just start comments with "
"\"##\" to import them to doxygen. This script is automatically configured, "
"when you use the autotools bootstrap from:\n"
- "https://dev.marc.waeckerlin.org/redmine/projects/bootstrap-build-environment";
+ "https://mrw.sh/development/bootstrap-build-environment";
}
/// set workdir
void path(QString path) {
_path = (path.size()?path:".")+QDir::separator();
}
/// get workdir
- QString path() {
+ QString path() const {
return _path;
}
+ /// get all command prototypes
+ const Prototypes& prototypes() const {
+ return _prototypes;
+ }
QString commands(Formatting f = PLAIN) const {
QString cmds;
- for (auto it(_prototypes.begin()); it!=_prototypes.end(); ++it)
- switch (f) {
- case PLAIN: {
+ switch (f) {
+ case PLAIN: {
+ for (auto it(_prototypes.begin()); it!=_prototypes.end(); ++it)
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;
+ } break;
+ case HTML: {
+ auto format = [](QString s) {
+ return s
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace(QRegularExpression("<([-_A-Za-z0-9]+)>"), "\\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", " ")
+ .replace(QRegularExpression("(http(s)?://[-/^a-z0-9.]+)"), "\\1 ");
+ };
+ cmds = ""
+ "
Contents "
+ ""
+ +format(syntax())+
+ "";
+ for (auto[name,command]: _prototypes) {
+ QStringList doc(command->description().split("\n\n--\n\n"));
+ assert(doc.size()==2); // description does not match expected format
+ QString usage(doc.takeFirst());
+ QString description(doc.takeFirst());
+ cmds += "Usage "
+ + format(usage)
+ + "
Description "
+ + format(description)
+ + "
";
+ }
+ } break;
}
return cmds.trimmed();
}
@@ -1155,7 +1178,6 @@ class Script: public QObject {
QString username;
QString password;
};
- typedef std::map> Prototypes;
typedef std::vector> Commands;
Prototypes _prototypes;
Commands _script;
@@ -1205,7 +1227,7 @@ class Do: public Command {
QString description() const {
return
tag()+" []\n \n "
- "\n\n"
+ "\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 "
"refere to the selected object within the scripy by \"this\". The "
@@ -1248,7 +1270,7 @@ class Load: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Load an URL, the URL is given as parameter in full syntax.";
}
QString command() const {
@@ -1277,7 +1299,7 @@ class Expect: public Command {
QString description() const {
return
tag()+" []"
- "\n\n"
+ "\n\n--\n\n"
"Expect a signal. Signals are emitted by webkit and may contain "
"parameter. If a parameter is given in the script, then the parameter "
"must match exactly. If no parameter is given, then the signal must "
@@ -1346,7 +1368,7 @@ class Open: public Command {
QString description() const {
return
tag()+
- "\n\n"
+ "\n\n--\n\n"
"Open the browser window, so you can follow the test steps visually.";
}
QString command() const {
@@ -1372,7 +1394,7 @@ class Sleep: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\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.";
@@ -1410,7 +1432,7 @@ class Exit: public Command {
QString description() const {
return
tag()+
- "\n\n"
+ "\n\n--\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.";
@@ -1437,7 +1459,7 @@ class IgnoreTo: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Ignore all following commands up to a given label. The following "
"commands are not executed until the given label appears in the "
"script. This helps when you debug your scripts and you "
@@ -1470,7 +1492,7 @@ class Label: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"This marks the label refered by command \"ignoreto\".";
}
QString command() const {
@@ -1500,7 +1522,7 @@ class Upload: public Command {
QString description() const {
return
tag()+" -> "
- "\n\n"
+ "\n\n--\n\n"
"Presses the specified file upload button and passes a given file "
"name. The command requires a CSS selector followed by a filename. "
"The first object that matches the selector is used.";
@@ -1554,7 +1576,7 @@ class Exists: public Command {
QString description() const {
return
tag()+" -> "
- "\n\n"
+ "\n\n--\n\n"
"Assert that a certain text exists in the selected object, or if no "
"text is given, assert that the specified object exists. The object "
"is given by a CSS selector. All matching objects are search for the "
@@ -1610,7 +1632,7 @@ class Not: public Command {
QString description() const {
return
tag()+" -> "
- "\n\n"
+ "\n\n--\n\n"
"Assert that a certain text does not exists in the selected object, "
"or if no text is given, assert that the specified object does not "
"exists. The object is given by a CSS selector. All matching objects "
@@ -1659,7 +1681,7 @@ class Execute: public Command {
QString description() const {
return
tag()+" \n \n \n <...>"
- "\n\n"
+ "\n\n--\n\n"
"Execute . The command can have space separated arguments. "
"Following lines that are intended by at least "
"one space are streamed into the command. This way, you can e.g. "
@@ -1726,7 +1748,7 @@ class Download: public Command {
return
tag()+" \n"
" "
- "\n\n"
+ "\n\n--\n\n"
"Set download file before loading a download link or clicking on a "
"download button. The next line must be exactly one command that "
"initiates the download.\n\n"
@@ -1807,7 +1829,7 @@ class Click: public Command {
QString description() const {
return
tag()+" [] "
- "\n\n"
+ "\n\n--\n\n"
"Click on the specified element. Either you explicitely specify a click"
" type, such as or , or the previously set or"
" the default clicktype is used.";
@@ -1861,10 +1883,10 @@ class Set: public Command {
}
QString description() const {
return
- tag()+" =\n"+
+ tag()+" =\n\n"+
tag()+" \n"
" "
- "\n\n"
+ "\n\n--\n\n"
"Sets the value of a variable either to a constant, or to the output"
" of a command. A command should be a command that produces an"
" output, such as , which returns the result of JavaScript or"
@@ -1920,7 +1942,7 @@ class UnSet: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Undo the setting of a variable. The opposite of «set».";
}
QString command() const {
@@ -1949,7 +1971,7 @@ class Timeout: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Set the timeout in seconds (defaults to 10).";
}
QString command() const {
@@ -1982,7 +2004,7 @@ class CaCertificate: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Load a CA certificate that will be accepted on SSL connections.";
}
QString command() const {
@@ -2018,7 +2040,7 @@ class ClientCertificate: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Load a client certificate to authenticate on SSL connections. "
"The password for the keyfile should not contain spaces. "
"Create the two files from a PKCS#12 file using OpenSSL:\n"
@@ -2078,7 +2100,7 @@ class ClickType: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\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"
@@ -2122,9 +2144,9 @@ class SetValue: public Command {
}
QString description() const {
return
- tag()+" -> ''\n"+
+ tag()+" -> ''\n\n"+
tag()+" -> '', '', <...>"
- "\n\n"
+ "\n\n--\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"
@@ -2191,7 +2213,7 @@ class Function: public CommandContainer {
" \n"
" \n"
" <...>"
- "\n\n"
+ "\n\n--\n\n"
"Define a function with arguments. The arguments are treated like"
" local variables. In a sequence of scripts within the same testrun,"
" functions are inherited from all followin scripts, so you can first"
@@ -2241,7 +2263,7 @@ class Call: public Command {
QString description() const {
return
tag()+" ['', '', ...]"
- "\n\n"
+ "\n\n--\n\n"
"Calls a function. The number of arguments must be exactly the same"
" as in the function definition.\n\n"
"If you quote the values, then quote all values with the same"
@@ -2296,7 +2318,7 @@ class If: public CommandContainer {
" \n"
" \n"
" <...>\n"
- "\n\n"
+ "\n\n--\n\n"
"Execute commands conditionally. "
"The first variant compares a variable to a value. "
"The comparision can be = ! . ^ ~ < >, "
@@ -2408,7 +2430,7 @@ class While: public CommandContainer {
" \n"
" \n"
" <...>\n"
- "\n\n"
+ "\n\n--\n\n"
"Repeats commands conditionally. "
"The first variant compares a variable to a value. "
"The comparision can be = ! . ^ ~ < >, "
@@ -2501,7 +2523,7 @@ class TestSuite: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Start a testsuite and give it a name.";
}
QString command() const {
@@ -2530,7 +2552,7 @@ class TestCase: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Start a testcase and give it a name.";
}
QString command() const {
@@ -2558,10 +2580,10 @@ class Check: public Command {
}
QString description() const {
return
- tag()+" \n"+
+ tag()+" \n\n"+
tag()+" \n"
" "
- "\n\n"
+ "\n\n--\n\n"
"Compares two values (you can use variables) or compares a value to the"
" result of a command. The command should be a command that produces an"
" output, such as , which returns the result of JavaScript or"
@@ -2644,7 +2666,7 @@ class For: public CommandContainer {
" \n"
" \n"
" <...>"
- "\n\n"
+ "\n\n--\n\n"
"Executes the given commands with the variable set to the specifier values,"
"repeated once per given value. The variable is treated like a local variale"
" in the loop.\n\n"
@@ -2696,7 +2718,7 @@ class Echo: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Echoes a text to the log.";
}
QString command() const {
@@ -2725,7 +2747,7 @@ class OfflineStoragePath: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Set offline storage path. Defaults to /tmp.";
}
QString command() const {
@@ -2758,7 +2780,7 @@ class ClearCookies: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Clear all cookies of given URL , or of the current URL if no"
" is specified.";
}
@@ -2794,7 +2816,7 @@ class Include: public CommandContainer {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Include a test script.";
}
QString command() const {
@@ -2861,7 +2883,7 @@ class Case: public Command {
" \n"
" \n"
" <...>\n"
- "\n\n"
+ "\n\n--\n\n"
"Execute commands conditionally depending on a variable. "
"It is equivalent to neested if-else-if commands."
"The first variant compares a variable to a value. "
@@ -2981,7 +3003,7 @@ class Fail: public Command {
QString description() const {
return
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Fail with error text.";
}
QString command() const {
@@ -3012,7 +3034,7 @@ class Auth: public Command {
tag()+" "
"\n\n"+
tag()+" "
- "\n\n"
+ "\n\n--\n\n"
"Set basic authentication credentials for to"
" and . If no realm is given,"
" the credentials for the given realm are removed.";
@@ -3054,7 +3076,7 @@ class Ignore: public Command {
QString description() const {
return
tag()+" <...>"
- "\n\n"
+ "\n\n--\n\n"
"Ignores a specific signal. It will not be placed in the queue "
"and any expect for this signal is always true. You can call "
"ignore with a space separated list of signal names. You cannot "
@@ -3086,7 +3108,7 @@ class UnIgnore: public Command {
QString description() const {
return
tag()+" <...>"
- "\n\n"
+ "\n\n--\n\n"
"Undo ignoring a specific signal. It will be placed in the queue "
"and any expect this signal checks the queue. You can call "
"unignore with a space separated list of signal names. You cannot "
@@ -3119,7 +3141,7 @@ class : public Command {
QString description() const {
return
tag()+
- "\n\n"
+ "\n\n--\n\n"
"";
}
QString command() const {
diff --git a/src/editor.hxx b/src/editor.hxx
index f12b9eb..54df68a 100644
--- a/src/editor.hxx
+++ b/src/editor.hxx
@@ -76,6 +76,13 @@ class CodeEditor: public QPlainTextEdit {
Q_SIGNALS:
void include(QString);
void link(QString);
+ public Q_SLOTS:
+ void gotoLine(int line) {
+ if (textCursor().blockNumber()==line-1) return;
+ QTextCursor cursor(document()->findBlockByNumber(line-1));
+ setTextCursor(cursor);
+ highlightCurrentLine();
+ }
public:
CodeEditor(QWidget *parent = 0): QPlainTextEdit(parent) {
Highlighter *highlighter(new Highlighter(document()));
@@ -87,11 +94,6 @@ class CodeEditor: public QPlainTextEdit {
updateLineNumberAreaWidth(0);
highlightCurrentLine();
}
- void gotoLine(int line) {
- QTextCursor cursor(document()->findBlockByNumber(line-1));
- setTextCursor(cursor);
- highlightCurrentLine();
- }
void lineNumberAreaPaintEvent(QPaintEvent *event) {
QPainter painter(lineNumberArea);
painter.fillRect(event->rect(), Qt::lightGray);
diff --git a/src/help.hxx b/src/help.hxx
new file mode 100644
index 0000000..b5aa948
--- /dev/null
+++ b/src/help.hxx
@@ -0,0 +1,54 @@
+#ifndef __HELP__HXX
+#define __HELP__HXX
+
+#include
+#include
+#include
+
+class Help: public QDockWidget, protected Ui::Help {
+ Q_OBJECT
+ public:
+ Help(QWidget* p = nullptr): QDockWidget(p) {
+ setupUi(this);
+ _search->hide();
+ _help->setText(script.commands(Script::HTML));
+ }
+ public Q_SLOTS:
+ void on__search_textEdited(const QString& txt) {
+ _help->moveCursor(QTextCursor::Start);
+ if (script.prototypes().count(txt))
+ _help->scrollToAnchor(txt);
+ else
+ _help->find(txt);
+ }
+ void on__search_returnPressed() {
+ next();
+ }
+ void next() {
+ _help->find(_search->text());
+ }
+ void previous() {
+ _help->find(_search->text(), QTextDocument::FindBackward);
+ }
+ void startSearch() {
+ _search->show();
+ _search->setFocus();
+ _search->selectAll();
+ }
+ protected:
+ void keyPressEvent(QKeyEvent *e) {
+ if (e->matches(QKeySequence::Find))
+ startSearch();
+ if (e->matches(QKeySequence::FindNext))
+ next();
+ if (e->matches(QKeySequence::FindPrevious))
+ previous();
+ if (e->matches(QKeySequence::Cancel))
+ _search->hide();
+ return QDockWidget::keyPressEvent(e);
+ }
+ private:
+ const Script script;
+};
+
+#endif
diff --git a/src/help.ui b/src/help.ui
new file mode 100644
index 0000000..fe6a5ca
--- /dev/null
+++ b/src/help.ui
@@ -0,0 +1,36 @@
+
+
+ Help
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Help?!?
+
+
+
+ -
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/src/makefile.am b/src/makefile.am
index adb16bd..670405b 100644
--- a/src/makefile.am
+++ b/src/makefile.am
@@ -8,9 +8,10 @@
bin_PROGRAMS = webtester webrunner
-webtester_MOCFILES = moc_testgui.cxx moc_scriptfile.cxx moc_commands.cxx moc_webpage.cxx \
+webtester_MOCFILES = moc_testgui.cxx moc_scriptfile.cxx moc_help.cxx \
+ moc_commands.cxx moc_webpage.cxx \
moc_networkaccessmanager.cxx moc_editor.cxx
-webtester_UIFILES = ui_testgui.hxx ui_scriptfile.hxx
+webtester_UIFILES = ui_testgui.hxx ui_scriptfile.hxx ui_help.hxx
webtester_SOURCES = version.cxx webtester.cxx exceptions.hxx version.hxx \
${webtester_MOCFILES} ${webtester_UIFILES}
diff --git a/src/scriptfile.hxx b/src/scriptfile.hxx
index 8448732..47de321 100644
--- a/src/scriptfile.hxx
+++ b/src/scriptfile.hxx
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
@@ -16,17 +17,18 @@ class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
void include(QString);
void close(ScriptFile*);
void run(const QString&, const QString&, bool, Script&);
- void running(QString file, int line);
+ void progress(QString, int, int, int);
public:
- ScriptFile(QWidget* p = nullptr): QDockWidget(p) {
+ ScriptFile(QWidget* p = nullptr, QString name = QString()): QDockWidget(p), _name(name) {
setupUi(this);
+ setWindowTitle(_name+"[*]");
assert(connect(_editor, SIGNAL(textChanged()), SLOT(modified())));
assert(connect(_editor, SIGNAL(include(QString)), SIGNAL(include(QString))));
assert(connect(_editor, SIGNAL(link(QString)), SIGNAL(link(QString))));
+ assert(connect(_editor, SIGNAL(cursorPositionChanged()), SLOT(setLine())));
+ assert(connect(_line, SIGNAL(valueChanged(int)), SLOT(gotoLine(int))));
_searchBar->hide();
_replaceBar->hide();
- _lineBar->hide();
- _progress->hide();
_status->setCurrentIndex(STATUS_NONE);
}
CodeEditor* editor() {
@@ -40,10 +42,14 @@ class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
setWindowTitle(name+"[*]");
setWindowModified(false);
}
+ public Q_SLOTS:
+ void setLine() {
+ int pos(_editor->textCursor().blockNumber()+1);
+ if (pos) _line->setValue(pos);
+ }
void gotoLine(int line) {
_editor->gotoLine(line);
}
- public Q_SLOTS:
void load(QString name = QString()) {
if (isWindowModified() &&
QMessageBox::question(this, tr("Changes Not Saved"),
@@ -108,8 +114,6 @@ class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
modified(this);
}
void run() {
- _progress->reset();
- _progress->show();
_status->setCurrentIndex(STATUS_RUNNING);
bool oldRecordState(_record->isChecked());
_record->setChecked(false);
@@ -118,9 +122,7 @@ class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
Script script;
try {
assert(connect(&script, SIGNAL(progress(QString, int, int, int)),
- SLOT(progress(QString, int, int, int))));
- assert(connect(&script, SIGNAL(progress(QString, int, int, int)),
- SIGNAL(running(QString, int))));
+ SIGNAL(progress(QString, int, int, int))));
QString text(_editor->textCursor().selection().toPlainText());
if (text.isEmpty()) text = _editor->toPlainText();
run(_name, text, _screenshots->isChecked(), script);
@@ -158,7 +160,6 @@ class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
_run->setEnabled(true);
_record->setEnabled(true);
_record->setChecked(oldRecordState);
- _progress->hide();
}
void appendCommand(const QString& txt) {
if (!_record->isChecked()) return;
@@ -225,22 +226,69 @@ class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
_editor->moveCursor(QTextCursor::End);
_editor->ensureCursorVisible();
}
- void progress(const QString& file, int line, int pos, int max) {
- _progress->setFormat(QString("%1:%2 — %p%").arg(file).arg(line));
- _progress->setMinimum(0);
- _progress->setMaximum(max);
- _progress->setValue(pos);
- }
void runEnabled(bool f = true) {
- _run->setEnabled(false);
+ _run->setEnabled(f);
}
void on__run_clicked() {
run();
}
+ void on__from_textEdited(const QString& txt) {
+ on__next_clicked();
+ }
+ void on__next_clicked() {
+ if (_regex->isChecked())
+ _editor->find(QRegExp(_from->text()));
+ else
+ _editor->find(_from->text());
+ }
+ void on__previous_clicked() {
+ if (_regex->isChecked())
+ _editor->find(QRegExp(_from->text()), QTextDocument::FindBackward);
+ else
+ _editor->find(_from->text(), QTextDocument::FindBackward);
+ }
+ void on__replace_clicked() {
+ QTextCursor tc(_editor->textCursor());
+ if (tc.hasSelection()) tc.insertText(_to->text());
+ }
+ void on__replaceAll_clicked() {
+ QTextCursor cursor(_editor->textCursor());
+ if (_regex->isChecked())
+ _editor->setPlainText(_editor->toPlainText().replace(QRegExp(_from->text()), _to->text()));
+ else
+ _editor->setPlainText(_editor->toPlainText().replace(_from->text(), _to->text()));
+ _editor->setTextCursor(cursor);
+ }
+ void startSearch() {
+ _searchBar->show();
+ _from->setFocus();
+ _from->selectAll();
+ }
+ void startReplace() {
+ _searchBar->show();
+ _replaceBar->show();
+ _to->setFocus();
+ _to->selectAll();
+ }
protected:
void closeEvent(QCloseEvent*) {
close(this);
}
+ void keyPressEvent(QKeyEvent *e) {
+ if (e->matches(QKeySequence::Find))
+ startSearch();
+ if (e->matches(QKeySequence::Replace))
+ startReplace();
+ if (e->matches(QKeySequence::FindNext))
+ on__next_clicked();
+ if (e->matches(QKeySequence::FindPrevious))
+ on__previous_clicked();
+ if (e->matches(QKeySequence::Cancel)) {
+ _searchBar->hide();
+ _replaceBar->hide();
+ }
+ return QDockWidget::keyPressEvent(e);
+ }
private:
enum RunStatus {
STATUS_NONE = 0,
diff --git a/src/scriptfile.ui b/src/scriptfile.ui
index 961d008..9818e20 100644
--- a/src/scriptfile.ui
+++ b/src/scriptfile.ui
@@ -133,6 +133,13 @@
+ -
+
+
+ 999999999
+
+
+
@@ -158,9 +165,9 @@
-
-
+
- all
+ RegularExpression
@@ -181,38 +188,15 @@
-
-
-
- RegularExpression
-
-
-
-
-
-
- -
-
-
- -
-
-
- -
-
+
- line
+ replace all
- -
-
-
- 24
-
-
-
@@ -224,5 +208,38 @@
-
+
+
+ _from
+ returnPressed()
+ _next
+ click()
+
+
+ 259
+ 496
+
+
+ 303
+ 505
+
+
+
+
+ _to
+ returnPressed()
+ _replace
+ click()
+
+
+ 316
+ 564
+
+
+ 562
+ 558
+
+
+
+
diff --git a/src/testgui.hxx b/src/testgui.hxx
index 558f1f5..451feea 100644
--- a/src/testgui.hxx
+++ b/src/testgui.hxx
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -42,14 +43,16 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
setDockOptions(dockOptions()|QMainWindow::GroupedDragging);
#endif
menuViews->addAction(_setupScriptDock->toggleViewAction());
- menuViews->addAction(_scriptCommandsDock->toggleViewAction());
+ menuViews->addAction(_helpDock->toggleViewAction());
menuViews->addAction(_domDock->toggleViewAction());
menuViews->addAction(_linksDock->toggleViewAction());
menuViews->addAction(_formsDock->toggleViewAction());
menuViews->addAction(_logDock->toggleViewAction());
menuViews->addAction(_sourceDock->toggleViewAction());
menuViews->addAction(_executeDock->toggleViewAction());
-
+ menuHelp->addAction(_helpDock->toggleViewAction());
+ statusbar->addPermanentWidget(_progress = new QProgressBar(), 1);
+ _progress->hide();
QSettings settings("mrw", "webtester");
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowstate").toByteArray());
@@ -64,7 +67,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
_web->setPage(pg);
_web->installEventFilter(this); // track mouse and keyboard
pg->setForwardUnsupportedContent(true);
- _commands->setText(Script().commands(Script::HTML));
assert(connect(menuFile, SIGNAL(aboutToShow()), SLOT(fileMenuOpened())));
assert(connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)),
SLOT(focusChanged(QWidget*, QWidget*))));
@@ -89,6 +91,26 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
enterText(true);
_web->stop();
}
+ void on__actionAbout_triggered() {
+ QMessageBox::about(this, tr("About"),
+ tr("Webtester\n"
+ "Version: %1\n"
+ "Builddate: %2\n"
+ "Release: %3\n"
+ "Libraries:\n"
+ "qt-%4 (%5)\n")
+ .arg(VERSION)
+ .arg(BUILD_DATE)
+ .arg(PACKAGE_VERSION)
+ .arg(qVersion())
+ .arg(QT_VERSION_STR));
+ }
+ void on__actionNew_triggered() {
+ QString name(QFileDialog::getSaveFileName(this, tr("Filename for new Test Script")));
+ QFileInfo info(name);
+ if (info.absoluteDir()==QDir::current()) name = info.fileName();
+ if (!name.isEmpty()) newScript(name);
+ }
void on__actionOpen_triggered() {
QString name(QFileDialog::getOpenFileName(this, tr("Open Test Script")));
if (name.isEmpty()) return;
@@ -117,10 +139,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
ScriptFile* active(activeScriptFile());
if (active) active->clear();
}
- void on__actionRun_triggered() {
- ScriptFile* active(activeScriptFile());
- if (active) active->run();
- }
void on__focused_clicked() {
enterText(true);
QWebElement element(focused());
@@ -239,7 +257,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
_actionSaveAs->setEnabled(active);
_actionSave->setEnabled(active&&active->isWindowModified());
_actionClear->setEnabled(active);
- _actionRun->setEnabled(active);
}
QString activate(QString name) {
QFileInfo info(name);
@@ -247,7 +264,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
if (!_testscripts.contains(name)) return load(name);
_testscripts[name]->show();
_testscripts[name]->raise();
- _testscripts[name]->activateWindow();
return name;
}
QString load(QString name) {
@@ -259,24 +275,29 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
} catch(const std::exception& x) {
remove(_testscripts[name]);
}
- QDockWidget* first(_testscripts.isEmpty()?_setupScriptDock:_testscripts.last());
- _testscripts[name] = new ScriptFile(this);
- assert(connect(_testscripts[name], SIGNAL(modified(ScriptFile*)), SLOT(modified(ScriptFile*))));
- assert(connect(_testscripts[name], SIGNAL(link(QString)), SLOT(activate(QString))));
- assert(connect(_testscripts[name], SIGNAL(close(ScriptFile*)), SLOT(remove(ScriptFile*))));
- assert(connect(_testscripts[name], SIGNAL(run(const QString&, const QString&, bool, Script&)),
- SLOT(run(const QString&, const QString&, bool, Script&))));
- assert(connect(_testscripts[name], SIGNAL(running(QString, int)),
- SLOT(showFileLine(QString, int))));
+ newScript(name);
try {
_testscripts[name]->load(name);
- tabifyDockWidget(first, _testscripts[name]);
return activate(name);
} catch(const std::exception& x) {
remove(_testscripts[name]);
return QString();
}
}
+ QString newScript(QString name) {
+ if (_testscripts.contains(name)) return activate(name);
+ QDockWidget* first(_testscripts.isEmpty()?_setupScriptDock:_testscripts.last());
+ _testscripts[name] = new ScriptFile(this, name);
+ assert(connect(_testscripts[name], SIGNAL(modified(ScriptFile*)), SLOT(modified(ScriptFile*))));
+ assert(connect(_testscripts[name], SIGNAL(link(QString)), SLOT(activate(QString))));
+ assert(connect(_testscripts[name], SIGNAL(close(ScriptFile*)), SLOT(remove(ScriptFile*))));
+ assert(connect(_testscripts[name], SIGNAL(run(const QString&, const QString&, bool, Script&)),
+ SLOT(run(const QString&, const QString&, bool, Script&))));
+ assert(connect(_testscripts[name], SIGNAL(progress(QString, int, int, int)),
+ SLOT(progress(QString, int, int, int))));
+ tabifyDockWidget(first, _testscripts[name]);
+ return name;
+ }
void remove(ScriptFile* scriptfile) {
/// @todo check if modified
_testscripts.remove(scriptfile->name());
@@ -294,7 +315,12 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
script.parse(text.split('\n'), name);
script.run(_web->page()->mainFrame(), testsuites, QString(), screenshots);
}
- void showFileLine(QString file, int line) {
+ void progress(QString file, int line, int pos, int max) {
+ _progress->setVisible(max>0&&pos!=max);
+ _progress->setFormat(QString("%1:%2 — %p%").arg(file).arg(line));
+ _progress->setMinimum(0);
+ _progress->setMaximum(max);
+ _progress->setValue(pos);
if (!_actionCursorFollowsFiles->isChecked() || file.isEmpty() || file=="setup") return;
try {
QString name(activate(file));
@@ -304,10 +330,9 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
}
protected:
ScriptFile* activeScriptFile(QWidget* focus=nullptr) {
- //for (auto win: _testscripts) if (win->isActiveWindow()) return win;
ScriptFile* active(nullptr);
for (QObject* wid(focus?focus:QApplication::focusWidget()); !active && wid; wid = wid->parent())
- active = dynamic_cast(wid);
+ active = qobject_cast(wid);
return active;
}
void closeEvent(QCloseEvent* event) {
@@ -324,7 +349,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
}
QMainWindow::closeEvent(event);
}
- bool eventFilter(QObject*, QEvent* event) {
+ bool eventFilter(QObject* target, QEvent* event) {
if (_inEventFilter) return false;
_inEventFilter = true;
enterText();
@@ -829,6 +854,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
bool _inEventFilter; // actually handling event filter
Script _setupScript;
QMap _testscripts;
+ QProgressBar* _progress = nullptr;
};
#endif // TESTGUI_HXX
diff --git a/src/testgui.ui b/src/testgui.ui
index 70121da..13e630f 100644
--- a/src/testgui.ui
+++ b/src/testgui.ui
@@ -6,8 +6,8 @@
0
0
- 888
- 1180
+ 852
+ 759
@@ -103,7 +103,7 @@
0
0
- 888
+ 852
34
@@ -116,6 +116,7 @@
F&ile
+
@@ -123,15 +124,14 @@
-
-
+
- Script &Commands
+ &Help
4
-
-
- -
-
-
- true
-
-
-
-
-
@@ -588,17 +577,6 @@ this.dispatchEvent(evObj);
Ctrl+Q
-
-
- false
-
-
- &Run
-
-
- Ctrl+R
-
-
Run &Line
@@ -650,9 +628,9 @@ this.dispatchEvent(evObj);
O&pen Setup Script ...
-
+
- &Commands ...
+ &About
@@ -663,12 +641,17 @@ this.dispatchEvent(evObj);
true
- Cursor Follows Files
+ &Cursor Follows Files
<html><head/><body><p>When running a script, the currently running file and line is highlighted. So the cursor follows the current file and line when running a script.</p></body></html>
+
+
+ &New ...
+
+
@@ -681,12 +664,20 @@ this.dispatchEvent(evObj);
QPlainTextEdit
+
+ Help
+ QDockWidget
+
+ 1
+
_load
_web
-
+
+
+
_selector
@@ -695,12 +686,12 @@ this.dispatchEvent(evObj);
click()
- 785
- 1144
+ 267
+ 589
- 875
- 1075
+ 360
+ 496
@@ -711,12 +702,12 @@ this.dispatchEvent(evObj);
click()
- 785
- 1144
+ 267
+ 589
- 874
- 1144
+ 359
+ 589
@@ -731,8 +722,8 @@ this.dispatchEvent(evObj);
-1
- 630
- 971
+ 369
+ 599
@@ -743,8 +734,8 @@ this.dispatchEvent(evObj);
setChecked(bool)
- 630
- 971
+ 369
+ 599
-1
@@ -763,8 +754,8 @@ this.dispatchEvent(evObj);
-1
- 748
- 374
+ 851
+ 337
@@ -775,8 +766,8 @@ this.dispatchEvent(evObj);
setChecked(bool)
- 748
- 374
+ 851
+ 337
-1
@@ -795,8 +786,8 @@ this.dispatchEvent(evObj);
-1
- 748
- 537
+ 851
+ 468
@@ -807,8 +798,8 @@ this.dispatchEvent(evObj);
setChecked(bool)
- 748
- 537
+ 851
+ 468
-1
@@ -827,8 +818,8 @@ this.dispatchEvent(evObj);
-1
- 748
- 699
+ 851
+ 599
@@ -839,8 +830,8 @@ this.dispatchEvent(evObj);
setChecked(bool)
- 748
- 699
+ 851
+ 599
-1
@@ -860,7 +851,7 @@ this.dispatchEvent(evObj);
182
- 971
+ 730
@@ -872,7 +863,7 @@ this.dispatchEvent(evObj);
182
- 971
+ 730
-1
@@ -891,8 +882,8 @@ this.dispatchEvent(evObj);
-1
- 842
- 1004
+ 851
+ 730
@@ -903,8 +894,8 @@ this.dispatchEvent(evObj);
setChecked(bool)
- 842
- 1004
+ 851
+ 730
-1
@@ -935,12 +926,12 @@ this.dispatchEvent(evObj);
click()
- 277
- 315
+ 513
+ 257
- 549
- 315
+ 603
+ 258