new features implemented, switch to Version 3.0; done: find, replace, goto line

master
Marc Wäckerlin 6 years ago
parent b513f940df
commit 2133615a4c
  1. 5
      configure.ac
  2. 172
      src/commands.hxx
  3. 12
      src/editor.hxx
  4. 54
      src/help.hxx
  5. 36
      src/help.ui
  6. 5
      src/makefile.am
  7. 84
      src/scriptfile.hxx
  8. 73
      src/scriptfile.ui
  9. 72
      src/testgui.hxx
  10. 129
      src/testgui.ui

@ -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)

@ -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()+" <filename-base>"
"\n\n"
"\n\n--\n\n"
"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 "
"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<QString, std::shared_ptr<Command>> Prototypes;
typedef std::pair<QString, QStringList> 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+="<h1>"+it->first+"</h1><p>"
+it->second->description()
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace(QRegularExpression("&lt;([^ ]+)&gt;"),
"<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&nbsp;&nbsp;")
.replace("\n\n", "</p><p>")
.replace("\n", "<br/>")
+"</p>";
} break;
} break;
case HTML: {
auto format = [](QString s) {
return s
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace(QRegularExpression("&lt;([-_A-Za-z0-9]+)&gt;"), "<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&nbsp;&nbsp;")
.replace("\n\n", "</p><p>")
.replace("\n", "<br/>")
.replace(QRegularExpression("(http(s)?://[-/^a-z0-9.]+)"), "<a href=\"\\1\">\\1</a>");
};
cmds = "<style>div#table-of-contents {border: 2px solid black; background-color: red; width: 100%; column-width: 5em;}</style>"
"<h1>Contents</h1><ul>"
"<li id=\"top-syntax-description\"><a href=\"#syntax-description\">Syntax</a></li>"
"<li id=\"top-command-list\"><a href=\"#command-list\">Commands</a><ul>";
for (auto[name,command]: _prototypes)
cmds += "<li id=\"top-"+name+"\"><a href=\"#"+name+"\">"+name+"</a></li>";
cmds += "</ul></li></ul>"
"<h1 id=\"syntax-description\"><a href=\"#top-syntax-description\">Syntax</a></h1>"
+format(syntax())+
"<h1 id=\"command-list\"><a href=\"#top-command-list\">Commands</a></h1>";
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 += "<h2 id=\""+name+"\"><a href=\"#top-"+name+"\">"+name+"</a></h2><h3>Usage</h3><p>"
+ format(usage)
+ "</p><h3>Description</h3><p>"
+ format(description)
+ "</p>";
}
} break;
}
return cmds.trimmed();
}
@ -1155,7 +1178,6 @@ class Script: public QObject {
QString username;
QString password;
};
typedef std::map<QString, std::shared_ptr<Command>> Prototypes;
typedef std::vector<std::shared_ptr<Command>> Commands;
Prototypes _prototypes;
Commands _script;
@ -1205,7 +1227,7 @@ class Do: public Command {
QString description() const {
return
tag()+" [<selector>]\n <javascript-line1>\n <javascript-line2>"
"\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()+" <url>"
"\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()+" <signal> [<parameter>]"
"\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()+" <seconds>"
"\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()+" <label>"
"\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()+" <label>"
"\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()+" <selector> -> <filename>"
"\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()+" <selector> -> <text>"
"\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()+" <selector> -> <text>"
"\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()+" <command>\n <line1>\n <line2>\n <...>"
"\n\n"
"\n\n--\n\n"
"Execute <command>. 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()+" <filename>\n"
" <command-to-start-download>"
"\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()+" [<clicktype>] <selector>"
"\n\n"
"\n\n--\n\n"
"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.";
@ -1861,10 +1883,10 @@ class Set: public Command {
}
QString description() const {
return
tag()+" <variable>=<value>\n"+
tag()+" <variable>=<value>\n\n"+
tag()+" <variable>\n"
" <command>"
"\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 <do>, which returns the result of JavaScript or"
@ -1920,7 +1942,7 @@ class UnSet: public Command {
QString description() const {
return
tag()+" <variable>"
"\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()+" <seconds>"
"\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()+" <filename.pem>"
"\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()+" <certfile.pem> <keyfile.pem> <keypassword>"
"\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()+" <type>"
"\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()+" <selector> -> '<value>'\n"+
tag()+" <selector> -> '<value>'\n\n"+
tag()+" <selector> -> '<value1>', '<value2>', <...>"
"\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 {
" <command1>\n"
" <command2>\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()+" <name> ['<arg1>', '<arg2>', ...]"
"\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 {
" <command3>\n"
" <command4>\n"
" <...>\n"
"\n\n"
"\n\n--\n\n"
"Execute commands conditionally. "
"The first variant compares a variable to a value. "
"The comparision <cmp> can be = ! . ^ ~ < >, "
@ -2408,7 +2430,7 @@ class While: public CommandContainer {
" <command1>\n"
" <command2>\n"
" <...>\n"
"\n\n"
"\n\n--\n\n"
"Repeats commands conditionally. "
"The first variant compares a variable to a value. "
"The comparision <cmp> can be = ! . ^ ~ < >, "
@ -2501,7 +2523,7 @@ class TestSuite: public Command {
QString description() const {
return
tag()+" <name>"
"\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()+" <name>"
"\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()+" <value1> <cmp> <value2>\n"+
tag()+" <value1> <cmp> <value2>\n\n"+
tag()+" <value1> <cmp>\n"
" <command>"
"\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 <do>, which returns the result of JavaScript or"
@ -2644,7 +2666,7 @@ class For: public CommandContainer {
" <command1>\n"
" <command2>\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()+" <text>"
"\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()+" <path>"
"\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()+" <url>"
"\n\n"
"\n\n--\n\n"
"Clear all cookies of given URL <url>, or of the current URL if no"
" <url> is specified.";
}
@ -2794,7 +2816,7 @@ class Include: public CommandContainer {
QString description() const {
return
tag()+" <filename>"
"\n\n"
"\n\n--\n\n"
"Include a test script.";
}
QString command() const {
@ -2861,7 +2883,7 @@ class Case: public Command {
" <command>\n"
" <command>\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()+" <text>"
"\n\n"
"\n\n--\n\n"
"Fail with error text.";
}
QString command() const {
@ -3012,7 +3034,7 @@ class Auth: public Command {
tag()+" <realm> <username> <password>"
"\n\n"+
tag()+" <realm>"
"\n\n"
"\n\n--\n\n"
"Set basic authentication credentials for <realm> to"
" <username> and <password>. 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()+" <signal1> <signal2> <...>"
"\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()+" <signal1> <signal2> <...>"
"\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 {

@ -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);

@ -0,0 +1,54 @@
#ifndef __HELP__HXX
#define __HELP__HXX
#include <ui_help.hxx>
#include <commands.hxx>
#include <cassert>
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

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Help</class>
<widget class="QDockWidget" name="Help">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Help?!?</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextBrowser" name="_help">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="_search"/>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

@ -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}

@ -5,6 +5,7 @@
#include <ui_scriptfile.hxx>
#include <QMessageBox>
#include <QScrollBar>
#include <QShortcut>
#include <QTextDocumentFragment>
#include <cassert>
@ -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,

@ -133,6 +133,13 @@
</widget>
</widget>
</item>
<item>
<widget class="QSpinBox" name="_line">
<property name="maximum">
<number>999999999</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -158,9 +165,9 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="_all">
<widget class="QCheckBox" name="_regex">
<property name="text">
<string>all</string>
<string>RegularExpression</string>
</property>
</widget>
</item>
@ -181,38 +188,15 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="_regex">
<property name="text">
<string>RegularExpression</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="_lineBar" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QSpinBox" name="_line"/>
</item>
<item>
<widget class="QPushButton" name="_goLine">
<widget class="QPushButton" name="_replaceAll">
<property name="text">
<string>line</string>
<string>replace all</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QProgressBar" name="_progress">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
@ -224,5 +208,38 @@
</customwidget>
</customwidgets>
<resources/>
<connections/>
<connections>
<connection>
<sender>_from</sender>
<signal>returnPressed()</signal>
<receiver>_next</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>259</x>
<y>496</y>
</hint>
<hint type="destinationlabel">
<x>303</x>
<y>505</y>
</hint>
</hints>
</connection>
<connection>
<sender>_to</sender>
<signal>returnPressed()</signal>
<receiver>_replace</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>564</y>
</hint>
<hint type="destinationlabel">
<x>562</x>
<y>558</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -19,6 +19,7 @@
#include <QScrollBar>
#include <QFile>
#include <QMessageBox>
#include <QShortcut>
#include <QCompleter>
#include <ui_testgui.hxx>
#include <stdexcept>
@ -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<ScriptFile*>(wid);
active = qobject_cast<ScriptFile*>(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<QString, ScriptFile*> _testscripts;
QProgressBar* _progress = nullptr;
};
#endif // TESTGUI_HXX

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>888</width>
<height>1180</height>
<width>852</width>
<height>759</height>
</rect>
</property>
<property name="focusPolicy">
@ -103,7 +103,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>888</width>
<width>852</width>
<height>34</height>
</rect>
</property>
@ -116,6 +116,7 @@
<property name="title">
<string>F&amp;ile</string>
</property>
<addaction name="_actionNew"/>
<addaction name="_actionOpen"/>
<addaction name="_actionOpenSetupScript"/>
<addaction name="separator"/>
@ -123,15 +124,14 @@
<addaction name="_actionSaveAs"/>
<addaction name="_actionRevertToSaved"/>
<addaction name="_actionClear"/>
<addaction name="_actionRun"/>
<addaction name="separator"/>
<addaction name="_actionQuit"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
<string>&amp;Help</string>
</property>
<addaction name="_actionCommands"/>
<addaction name="_actionAbout"/>
</widget>
<widget class="QMenu" name="menuOptions">
<property name="title">
@ -254,7 +254,7 @@
<string>Execute &amp;JavaScript on First Selected Item</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_10">
<layout class="QVBoxLayout" name="verticalLayout_5">
@ -476,24 +476,13 @@ this.dispatchEvent(evObj);</string>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="_scriptCommandsDock">
<widget class="Help" name="_helpDock">
<property name="windowTitle">
<string>Script &amp;Commands</string>
<string>&amp;Help</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">
<property name="checkable">
@ -588,17 +577,6 @@ this.dispatchEvent(evObj);</string>
<string>Ctrl+Q</string>
</property>
</action>
<action name="_actionRun">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Run</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="_actionRunLine">
<property name="text">
<string>Run &amp;Line</string>
@ -650,9 +628,9 @@ this.dispatchEvent(evObj);</string>
<string>O&amp;pen Setup Script ...</string>
</property>
</action>
<action name="_actionCommands">
<action name="_actionAbout">
<property name="text">
<string>&amp;Commands ...</string>
<string>&amp;About</string>
</property>
</action>
<action name="_actionCursorFollowsFiles">
@ -663,12 +641,17 @@ this.dispatchEvent(evObj);</string>
<bool>true</bool>
</property>
<property name="text">
<string>Cursor Follows Files</string>
<string>&amp;Cursor Follows Files</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</action>
<action name="_actionNew">
<property name="text">
<string>&amp;New ...</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@ -681,12 +664,20 @@ this.dispatchEvent(evObj);</string>
<extends>QPlainTextEdit</extends>
<header>editor.hxx</header>
</customwidget>
<customwidget>
<class>Help</class>
<extends>QDockWidget</extends>
<header>help.hxx</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>_load</tabstop>
<tabstop>_web</tabstop>
</tabstops>
<resources/>
<resources>
<include location="ressourcen.qrc"/>
</resources>
<connections>
<connection>
<sender>_selector</sender>
@ -695,12 +686,12 @@ this.dispatchEvent(evObj);</string>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>785</x>
<y>1144</y>
<x>267</x>
<y>589</y>
</hint>
<hint type="destinationlabel">
<x>875</x>
<y>1075</y>
<x>360</x>
<y>496</y>
</hint>
</hints>
</connection>
@ -711,12 +702,12 @@ this.dispatchEvent(evObj);</string>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>785</x>
<y>1144</y>
<x>267</x>
<y>589</y>
</hint>
<hint type="destinationlabel">
<x>874</x>
<y>1144</y>
<x>359</x>
<y>589</y>
</hint>
</hints>
</connection>
@ -731,8 +722,8 @@ this.dispatchEvent(evObj);</string>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>630</x>
<y>971</y>
<x>369</x>
<y>599</y>
</hint>
</hints>
</connection>
@ -743,8 +734,8 @@ this.dispatchEvent(evObj);</string>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>630</x>
<y>971</y>
<x>369</x>
<y>599</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
@ -763,8 +754,8 @@ this.dispatchEvent(evObj);</string>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>748</x>
<y>374</y>
<x>851</x>
<y>337</y>
</hint>
</hints>
</connection>
@ -775,8 +766,8 @@ this.dispatchEvent(evObj);</string>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>748</x>
<y>374</y>
<x>851</x>
<y>337</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
@ -795,8 +786,8 @@ this.dispatchEvent(evObj);</string>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>748</x>
<y>537</y>
<x>851</x>
<y>468</y>
</hint>
</hints>
</connection>
@ -807,8 +798,8 @@ this.dispatchEvent(evObj);</string>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>748</x>
<y>537</y>
<x>851</x>
<y>468</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
@ -827,8 +818,8 @@ this.dispatchEvent(evObj);</string>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>748</x>
<y>699</y>
<x>851</x>
<y>599</y>
</hint>
</hints>
</connection>
@ -839,8 +830,8 @@ this.dispatchEvent(evObj);</string>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>748</x>
<y>699</y>
<x>851</x>
<y>599</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
@ -860,7 +851,7 @@ this.dispatchEvent(evObj);</string>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>971</y>
<y>730</y>
</hint>
</hints>
</connection>
@ -872,7 +863,7 @@ this.dispatchEvent(evObj);</string>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>971</y>
<y>730</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
@ -891,8 +882,8 @@ this.dispatchEvent(evObj);</string>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>842</x>
<y>1004</y>
<x>851</x>
<y>730</y>
</hint>
</hints>
</connection>
@ -903,8 +894,8 @@ this.dispatchEvent(evObj);</string>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>842</x>
<y>1004</y>
<x>851</x>
<y>730</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
@ -935,12 +926,12 @@ this.dispatchEvent(evObj);</string>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>277</x>
<y>315</y>
<x>513</x>
<y>257</y>
</hint>
<hint type="destinationlabel">
<x>549</x>
<y>315</y>
<x>603</x>
<y>258</y>
</hint>
</hints>
</connection>

Loading…
Cancel
Save