now, multi document editing works mostly as designed; unfortunately qt does not send an event when the visibility of a tab window changes

master
Marc Wäckerlin 6 years ago
parent 74910f249a
commit 9142588686
  1. 211
      src/scriptfile.hxx
  2. 140
      src/scriptfile.ui
  3. 332
      src/testgui.hxx
  4. 225
      src/testgui.ui
  5. 16
      src/webtester.cxx

@ -1,23 +1,32 @@
#ifndef __SCRIPTFILE__HXX
#define __SCRIPTFILE__HXX
#include <commands.hxx>
#include <ui_scriptfile.hxx>
#include <QMessageBox>
#include <QScrollBar>
#include <QTextDocumentFragment>
#include <cassert>
class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
Q_OBJECT
Q_SIGNALS:
void modified(ScriptFile*);
void link(QString);
void include(QString);
void close(ScriptFile*);
void run(const QString&, const QString&, bool, Script&);
public:
ScriptFile(QWidget* p=0): QDockWidget(p) {
ScriptFile(QWidget* p = nullptr): QDockWidget(p) {
setupUi(this);
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))));
_searchBar->hide();
_replaceBar->hide();
_pageBar->hide();
_lineBar->hide();
_progress->hide();
_status->setCurrentIndex(STATUS_NONE);
}
CodeEditor* editor() {
return _editor;
@ -30,10 +39,208 @@ class ScriptFile: public QDockWidget, protected Ui::ScriptFile {
setWindowTitle(name+"[*]");
setWindowModified(false);
}
public Q_SLOTS:
void load(QString name = QString()) {
if (isWindowModified() &&
QMessageBox::question(this, tr("Changes Not Saved"),
tr("Load script without saving changes?"))
!= QMessageBox::Yes)
return;
QString oldname(_name);
if (!name.isEmpty()) _name = name;
QFileInfo info(name);
if (info.absoluteDir()==QDir::current()) _name = info.fileName();
try {
QFile file(_name);
if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
throw std::runtime_error("file open failed");
_editor->setPlainText(QString::fromUtf8(file.readAll()));
if (file.error()!=QFileDevice::NoError)
throw std::runtime_error("file read failed");
setWindowTitle(_name+"[*]");
setWindowModified(false);
_status->setCurrentIndex(STATUS_NONE);
} catch(const std::exception& x) {
QMessageBox::critical(this, tr("Open Failed"),
tr("Reading test script failed, %2. "
"Cannot read test script from file %1.")
.arg(_name).arg(x.what()));
_name = oldname;
}
}
void save(QString name = QString()) {
QString oldname(_name);
if (!name.isEmpty()) _name = name;
QFile file(_name);
try {
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
throw std::runtime_error("file open failed");
QTextStream out(&file);
out<<_editor->toPlainText();
if (out.status()!=QTextStream::Ok)
throw std::runtime_error(std::string("file write failed (")
+char(out.status()+48)+")");
setWindowModified(false);
setWindowTitle(_name+"[*]");
} catch(const std::exception& x) {
QMessageBox::critical(this, tr("Save Failed"),
tr("Saving test script failed, %2. "
"Cannot write test script to file %1.")
.arg(_name).arg(x.what()));
_name = oldname;
}
}
void clear() {
if (isWindowModified() &&
QMessageBox::question(this, tr("Changes Not Saved"),
tr("Clear script without saving changes?"))
!= QMessageBox::Yes)
return;
_editor->clear();
setWindowModified(false);
}
void modified() {
setWindowModified(true);
modified(this);
}
void run() {
_progress->reset();
_progress->show();
_status->setCurrentIndex(STATUS_RUNNING);
bool oldRecordState(_record->isChecked());
_record->setChecked(false);
_record->setEnabled(false);
_run->setEnabled(false);
Script script;
try {
assert(connect(&script, SIGNAL(progress(QString, int, int)), SLOT(progress(QString, int, int))));
QString text(_editor->textCursor().selection().toPlainText());
if (text.isEmpty()) text = _editor->toPlainText();
run(_name, text, _screenshots->isChecked(), script);
_status->setCurrentIndex(STATUS_SUCCESS);
} catch (std::exception &x) {
_status->setCurrentIndex(STATUS_ERROR);
std::shared_ptr<Command> cmd(script.command());
if (cmd)
QMessageBox::critical(this,
tr("Test Failed"),
tr("<html>"
" <h1>Error [%1]</h1>"
" <dl>"
" <dt>Command:</dt><dd><code>%3</code></dd>"
" <dt>File:</dt><dd>%4</dd>"
" <dt>Line:</dt><dd>%5</dd>"
" <dt>Error Message:</dt><dd><pre>%2</pre></dd>"
" </dl>"
"</html>")
.arg(demangle(typeid(x).name()))
.arg(x.what())
.arg(cmd->command())
.arg(cmd->file())
.arg(cmd->line()));
else
QMessageBox::critical(this,
tr("Test Failed"),
tr("<html>"
" <h1>Error [%1]</h1>"
" <p><code>%2</code></p>"
"</html>")
.arg(demangle(typeid(x).name()))
.arg(QString(x.what()).replace("\n", "<br/>")));
}
_run->setEnabled(true);
_record->setEnabled(true);
_record->setChecked(oldRecordState);
_progress->hide();
}
void appendCommand(const QString& txt) {
if (!_record->isChecked()) return;
_editor->appendPlainText(txt);
QScrollBar *vb(_editor->verticalScrollBar());
_editor->moveCursor(QTextCursor::End);
_editor->ensureCursorVisible();
if (!vb) return;
vb->setValue(vb->maximum());
}
void appendCommand(const QString& selector, const QString& txt) {
if (!_record->isChecked()) return;
QString text(_editor->toPlainText());
QStringList lines(text.split("\n"));
bool changed(false);
while (lines.size() &&
(lines.last()=="click "+selector ||
lines.last().startsWith("setvalue "+selector+" -> "))) {
lines.removeLast();
changed = true;
}
if (changed) {
_editor->setPlainText(lines.join("\n"));
_editor->moveCursor(QTextCursor::End);
_editor->ensureCursorVisible();
}
appendCommand(txt);
}
void appendWebLoadFinished(bool ok) {
if (!_record->isChecked()) return;
QString text(_editor->toPlainText());
QStringList lines(text.split("\n"));
if (ok && lines.size()>1 &&
lines.last().startsWith("expect urlChanged") &&
lines.at(lines.size()-2)=="expect loadStarted") {
// replace three expect lines by one single line
QString url(lines.last().replace("expect urlChanged", "").trimmed());
lines.removeLast(); lines.removeLast();
_editor->setPlainText(lines.join("\n"));
_editor->moveCursor(QTextCursor::End);
_editor->ensureCursorVisible();
appendCommand("expect load "+url);
} else {
appendCommand("expect loadFinished "+QString(ok?"true":"false"));
}
}
void unsupportedContent(QNetworkReply* reply) {
if (!_record->isChecked()) return;
QString filename(reply->url().toString().split('/').last());
if (reply->header(QNetworkRequest::ContentDispositionHeader).isValid()) {
QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
.toString());
if (part.contains(QRegularExpression("attachment; *filename="))) {
part.replace(QRegularExpression(".*attachment; *filename="), "");
if (part.size()) filename = part;
}
}
QString text(_editor->toPlainText());
int pos1(text.lastIndexOf(QRegularExpression("^do ")));
int pos2(text.lastIndexOf(QRegularExpression("^load ")));
int pos3(text.lastIndexOf(QRegularExpression("^click ")));
text.insert(std::max({pos1, pos2, pos3}), "download "+filename);
_editor->setPlainText(text);
_editor->moveCursor(QTextCursor::End);
_editor->ensureCursorVisible();
}
void progress(const QString& txt, int pos, int max) {
_progress->setFormat(QString("%1 — %p%").arg(txt));
_progress->setMinimum(0);
_progress->setMaximum(max);
_progress->setValue(pos);
}
void runEnabled(bool f = true) {
_run->setEnabled(false);
}
void on__run_clicked() {
run();
}
protected:
void closeEvent(QCloseEvent*) {
close(this);
}
private:
enum RunStatus {
STATUS_NONE = 0,
STATUS_RUNNING,
STATUS_SUCCESS,
STATUS_ERROR
};
private:
QString _name;
};

@ -7,17 +7,136 @@
<x>0</x>
<y>0</y>
<width>628</width>
<height>378</height>
<height>593</height>
</rect>
</property>
<property name="windowTitle">
<string>DockW&amp;idget</string>
<string>Do&amp;ckWidget</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="CodeEditor" name="_editor"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QPushButton" name="_record">
<property name="text">
<string>Record</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_run">
<property name="text">
<string>Run</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="_screenshots">
<property name="text">
<string>Screenshots</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>28</height>
</size>
</property>
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QStackedWidget" name="_status">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_6"/>
<widget class="QWidget" name="page_5">
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;div style=&quot;font-size: xx-large&quot;&gt;⌛&lt;/div&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;div style=&quot;font-size: xx-large; color: green&quot;&gt;✔&lt;/div&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_4">
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;div style=&quot;font-size: xx-large; color: red&quot;&gt;✘&lt;/div&gt;</string>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="_searchBar" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
@ -72,21 +191,28 @@
</widget>
</item>
<item>
<widget class="QWidget" name="_pageBar" native="true">
<widget class="QWidget" name="_lineBar" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QSpinBox" name="_page"/>
<widget class="QSpinBox" name="_line"/>
</item>
<item>
<widget class="QPushButton" name="_goPage">
<widget class="QPushButton" name="_goLine">
<property name="text">
<string>page</string>
<string>line</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>

@ -24,7 +24,6 @@
#include <stdexcept>
#include <QNetworkReply>
#include <QEvent>
#include <QTextDocumentFragment>
#include <mrw/stdext.hxx>
class TestGUI: public QMainWindow, protected Ui::TestGUI {
@ -39,7 +38,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
_inEventFilter(false) {
setWindowTitle("[*]");
setupUi(this);
menuViews->addAction(_scriptDock->toggleViewAction());
setDockOptions(dockOptions()|QMainWindow::GroupedDragging);
menuViews->addAction(_setupScriptDock->toggleViewAction());
menuViews->addAction(_scriptCommandsDock->toggleViewAction());
menuViews->addAction(_domDock->toggleViewAction());
@ -48,8 +47,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
menuViews->addAction(_logDock->toggleViewAction());
menuViews->addAction(_sourceDock->toggleViewAction());
menuViews->addAction(_executeDock->toggleViewAction());
_progress->hide();
_status->setCurrentIndex(STATUS_NONE);
QSettings settings("mrw", "webtester");
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowstate").toByteArray());
@ -64,21 +62,21 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
_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*))));
assert(connect(pg, SIGNAL(uploadFile(QString)), SLOT(uploadFile(QString))));
assert(connect(pg, SIGNAL(unsupportedContent(QNetworkReply*)),
SLOT(unsupportedContent(QNetworkReply*))));
assert(connect(pg, SIGNAL(downloadRequested(const QNetworkRequest&)),
SLOT(downloadRequested(const QNetworkRequest&))));
//assert(connect(_testscript, SIGNAL(include(QString)), SLOT(include(QString))));
assert(connect(_testscript, SIGNAL(link(QString)), SLOT(include(QString))));
if (setupScript.size()) loadSetup(setupScript);
if (scriptFile.size()) loadFile(scriptFile);
if (scriptFile.size()) load(scriptFile);
}
virtual ~TestGUI() {}
public Q_SLOTS:
void on__load_clicked() {
enterText(true);
if (_record->isChecked())
appendCommand("load "+map(_url->currentText()));
storeUrl(_url->currentText());
_webprogress->setFormat(_url->currentText());
@ -91,123 +89,34 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
void on__actionOpen_triggered() {
QString name(QFileDialog::getOpenFileName(this, tr("Open Test Script")));
if (name.isEmpty()) return;
loadFile(name);
_status->setCurrentIndex(STATUS_NONE);
load(name);
}
void on__actionOpenSetupScript_triggered() {
QString name(QFileDialog::getOpenFileName(this, tr("Open Setup Script")));
if (name.isEmpty()) return;
loadSetup(name);
_status->setCurrentIndex(STATUS_NONE);
}
void on__actionRevertToSaved_triggered() {
loadFile(_filename);
_status->setCurrentIndex(STATUS_NONE);
ScriptFile* active(activeScriptFile());
if (active) active->load();
}
void on__actionSaveAs_triggered() {
ScriptFile* active(activeScriptFile());
if (!active) return;
QString name(QFileDialog::getSaveFileName(this, tr("Save Test Script")));
if (name.isEmpty()) return;
_filename = name;
on__actionSave_triggered();
_status->setCurrentIndex(STATUS_NONE);
if (!name.isEmpty()) active->save(name);
}
void on__actionSave_triggered() {
QFile file(_filename);
try {
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
throw std::runtime_error("file open failed");
QTextStream out(&file);
out<<_testscript->toPlainText();
if (out.status()!=QTextStream::Ok)
throw std::runtime_error(std::string("file write failed (")
+char(out.status()+48)+")");
_actionSave->setEnabled(true);
_actionRevertToSaved->setEnabled(true);
setWindowModified(false);
setWindowTitle(_filename+"[*]");
} catch(const std::exception& x) {
QMessageBox::critical(this, tr("Save Failed"),
tr("Saving test script failed, %2. "
"Cannot write test script to file %1.")
.arg(_filename).arg(x.what()));
}
_status->setCurrentIndex(STATUS_NONE);
ScriptFile* active(activeScriptFile());
if (active) active->save();
}
void on__actionClear_triggered() {
if (isWindowModified() &&
QMessageBox::question(this, tr("Changes Not Saved"),
tr("Clear script without saving changes?"))
!= QMessageBox::Yes)
return;
_testscript->clear();
_log->clear();
_filename.clear();
_actionSave->setEnabled(false);
_actionRevertToSaved->setEnabled(false);
setWindowTitle("[*]");
setWindowModified(false);
_status->setCurrentIndex(STATUS_NONE);
}
void on__run_clicked() {
_progress->reset();
_progress->show();
_status->setCurrentIndex(STATUS_RUNNING);
bool oldRecordState(_record->isChecked());
_record->setChecked(false);
_record->setEnabled(false);
_run->setEnabled(false);
Script script;
try {
connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString)));
connect(&script, SIGNAL(progress(QString, int, int)), SLOT(progress(QString, int, int)));
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuite"));
if (_setupscriptactive->isEnabled()
&& _setupscriptactive->isChecked()) {
script.parse(_setupscript->toPlainText().split('\n'), "setup");
script.run(_web->page()->mainFrame(), testsuites, QString(),
_screenshots->isChecked());
script.reset();
ScriptFile* active(activeScriptFile());
if (active) active->clear();
}
QString text(_testscript->textCursor().selection().toPlainText());
if (text.isEmpty()) text = _testscript->toPlainText();
script.parse(text.split('\n'), "script");
script.run(_web->page()->mainFrame(), testsuites, QString(),
_screenshots->isChecked());
_status->setCurrentIndex(STATUS_SUCCESS);
} catch (std::exception &x) {
_status->setCurrentIndex(STATUS_ERROR);
std::shared_ptr<Command> cmd(script.command());
if (cmd)
QMessageBox::critical(this,
tr("Test Failed"),
tr("<html>"
" <h1>Error [%1]</h1>"
" <dl>"
" <dt>Command:</dt><dd><code>%3</code></dd>"
" <dt>File:</dt><dd>%4</dd>"
" <dt>Line:</dt><dd>%5</dd>"
" <dt>Error Message:</dt><dd><pre>%2</pre></dd>"
" </dl>"
"</html>")
.arg(demangle(typeid(x).name()))
.arg(x.what())
.arg(cmd->command())
.arg(cmd->file())
.arg(cmd->line()));
else
QMessageBox::critical(this,
tr("Test Failed"),
tr("<html>"
" <h1>Error [%1]</h1>"
" <p><code>%2</code></p>"
"</html>")
.arg(demangle(typeid(x).name()))
.arg(QString(x.what()).replace("\n", "<br/>")));
}
_run->setEnabled(true);
_record->setEnabled(true);
_record->setChecked(oldRecordState);
_progress->hide();
void on__actionRun_triggered() {
ScriptFile* active(activeScriptFile());
if (active) active->run();
}
void on__focused_clicked() {
enterText(true);
@ -228,7 +137,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
}
void on__web_linkClicked(const QUrl& url) {
enterText(true);
if (_record->isChecked())
appendCommand("load "+map(url.url()));
}
void on__web_loadProgress(int progress) {
@ -237,7 +145,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
}
void on__web_loadStarted() {
enterText(true);
if (_record->isChecked())
appendCommand("expect "+map("loadStarted"));
_webprogress->setValue(0);
_urlStack->setCurrentIndex(PROGRESS_VIEW);
@ -252,7 +159,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
_webprogress->setFormat(url.url());
storeUrl(url);
enterText(true);
if (_record->isChecked())
appendCommand("expect "+map("urlChanged "+url.url()));
}
void on__web_selectionChanged() {
@ -262,24 +168,8 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
}
void on__web_loadFinished(bool ok) {
enterText(true);
if (_record->isChecked()) {
QString text(_testscript->toPlainText());
QStringList lines(text.split("\n"));
if (ok && lines.size()>1 &&
lines.last().startsWith("expect urlChanged") &&
lines.at(lines.size()-2)=="expect loadStarted") {
// replace three expect lines by one single line
QString url(lines.last().replace("expect urlChanged", "").trimmed());
lines.removeLast(); lines.removeLast();
_testscript->setPlainText(lines.join("\n"));
_testscript->moveCursor(QTextCursor::End);
_testscript->ensureCursorVisible();
appendCommand("expect "+map("load "+url));
} else {
appendCommand("expect "+map("loadFinished "
+QString(ok?"true":"false")));
}
}
for (auto testscript: _testscripts)
testscript->appendWebLoadFinished(ok);
_urlStack->setCurrentIndex(URL_VIEW);
on__web_selectionChanged();
setLinks();
@ -287,8 +177,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
setDom();
}
void on__setupscript_textChanged() {
bool oldRecordState(_record->isChecked());
_run->setEnabled(false);
for (auto testscript: _testscripts) testscript->runEnabled(false);
_setupscriptactive->setEnabled(false);
try {
_setupscriptstatus->setText(trUtf8("?"));
@ -305,8 +194,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
} catch (std::exception &x) {
_setupscriptstatus->setText(trUtf8(""));
}
_run->setEnabled(true);
_record->setChecked(oldRecordState);
for (auto testscript: _testscripts) testscript->runEnabled(true);
}
void on__forms_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
if (!item) return;
@ -318,31 +206,12 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
}
void uploadFile(QString filename) {
enterText(true);
if (_record->isChecked())
appendCommand("upload "+map(filename));
}
void unsupportedContent(QNetworkReply* reply) {
if (!_record->isChecked()) return;
QString filename(reply->url().toString().split('/').last());
if (reply->header(QNetworkRequest::ContentDispositionHeader).isValid()) {
QString part(reply->header(QNetworkRequest::ContentDispositionHeader)
.toString());
if (part.contains(QRegularExpression("attachment; *filename="))) {
part.replace(QRegularExpression(".*attachment; *filename="), "");
if (part.size()) filename = part;
}
}
QString text(_testscript->toPlainText());
int pos1(text.lastIndexOf(QRegularExpression("^do ")));
int pos2(text.lastIndexOf(QRegularExpression("^load ")));
int pos3(text.lastIndexOf(QRegularExpression("^click ")));
text.insert(std::max({pos1, pos2, pos3}), "download "+filename);
_testscript->setPlainText(text);
_testscript->moveCursor(QTextCursor::End);
_testscript->ensureCursorVisible();
for (auto testscript: _testscripts) testscript->unsupportedContent(reply);
}
void downloadRequested(const QNetworkRequest&) {
if (_record->isChecked())
appendCommand("download2");
}
void logging(const QString& txt) {
@ -351,49 +220,80 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
if (!vb) return;
vb->setValue(vb->maximum());
}
void progress(const QString& txt, int pos, int max) {
_progress->setFormat(QString("%1 — %p%").arg(txt));
_progress->setMinimum(0);
_progress->setMaximum(max);
_progress->setValue(pos);
void fileMenuOpened() {
focusChanged(nullptr, nullptr);
}
void appendCommand(const QString& txt) {
_testscript->appendPlainText(txt);
QScrollBar *vb(_testscript->verticalScrollBar());
_testscript->moveCursor(QTextCursor::End);
_testscript->ensureCursorVisible();
if (!vb) return;
vb->setValue(vb->maximum());
void modified(ScriptFile* win) {
focusChanged(nullptr, win);
}
void include(QString name) {
if (_testscripts.contains(name)) return;
void focusChanged(QWidget*, QWidget* focus) {
ScriptFile* active(activeScriptFile(focus));
if (active)
setWindowFilePath(active->name());
else
setWindowFilePath(QString());
_actionRevertToSaved->setEnabled(active);
_actionSaveAs->setEnabled(active);
_actionSave->setEnabled(active&&active->isWindowModified());
_actionClear->setEnabled(active);
_actionRun->setEnabled(active);
}
void activate(QString name) {
QFileInfo info(name);
if (info.absoluteDir()==QDir::current()) name = info.fileName();
if (!_testscripts.contains(name)) return load(name);
_testscripts[name]->show();
_testscripts[name]->raise();
_testscripts[name]->activateWindow();
}
void load(QString name) {
QFileInfo info(name);
if (info.absoluteDir()==QDir::current()) name = info.fileName();
if (_testscripts.contains(name)) try {
_testscripts[name]->load(name);
return activate(name);
} 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(include(QString)), SLOT(include(QString))));
assert(connect(_testscripts[name], SIGNAL(link(QString)), SLOT(include(QString))));
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*))));
QFile file(name);
assert(connect(_testscripts[name], SIGNAL(run(const QString&, const QString&, bool, Script&)), SLOT(run(const QString&, const QString&, bool, Script&))));
try {
if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
throw std::runtime_error("file open failed");
_testscripts[name]->editor()->setPlainText(QString::fromUtf8(file.readAll()));
if (file.error()!=QFileDevice::NoError)
throw std::runtime_error("file read failed");
_testscripts[name]->name(name);
tabifyDockWidget(_scriptDock, _testscripts[name]);
// QDockWidget* d(0);
// for (QWidget* w(QApplication::focusWidget()); w&&!(d=qobject_cast<QDockWidget*>(w));
// w=qobject_cast<QWidget*>(w->parent()));
// if (d) d->raise();
_testscripts[name]->raise();
_testscripts[name]->load(name);
tabifyDockWidget(first, _testscripts[name]);
activate(name);
} catch(const std::exception& x) {
remove(_testscripts[name]);
}
}
void remove(ScriptFile* scriptfile) {
/// @todo check if modified
_testscripts.remove(scriptfile->name());
delete scriptfile;
}
void run(const QString& name, const QString& text, bool screenshots, Script& script) {
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuite"));
assert(connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString))));
if (_setupscriptactive->isEnabled()
&& _setupscriptactive->isChecked()) {
script.parse(_setupscript->toPlainText().split('\n'), "setup");
script.run(_web->page()->mainFrame(), testsuites, QString(), screenshots);
script.reset();
}
script.parse(text.split('\n'), name);
script.run(_web->page()->mainFrame(), testsuites, QString(), screenshots);
}
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);
return active;
}
void closeEvent(QCloseEvent* event) {
QSettings settings("mrw", "webtester");
settings.setValue("geometry", saveGeometry());
@ -440,7 +340,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
case QEvent::MouseButtonRelease: {
enterText(true);
_lastFocused=element;
if (_record->isChecked()) {
if (!element.isNull()) {
QString selected(selector(element));
if (handleMooTools(_lastFocused)) {
@ -475,7 +374,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
} else {
appendCommand("# click, but where?");
}
}
} break;
case QEvent::MouseButtonPress: {
} break;
@ -505,26 +403,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
_url->setCurrentText(u.url());
}
}
void loadFile(QString name) {
QFile file(name);
try {
if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
throw std::runtime_error("file open failed");
_testscript->setPlainText(QString::fromUtf8(file.readAll()));
if (file.error()!=QFileDevice::NoError)
throw std::runtime_error("file read failed");
_filename = name;
_actionSave->setEnabled(true);
_actionRevertToSaved->setEnabled(true);
setWindowTitle(name+"[*]");
setWindowModified(false);
} catch(const std::exception& x) {
QMessageBox::critical(this, tr("Open Failed"),
tr("Reading test script failed, %2. "
"Cannot read test script from file %1.")
.arg(name).arg(x.what()));
}
}
void loadSetup(QString name) {
QFile file(name);
try {
@ -644,41 +522,26 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
return in;
}
void javascript(const QString& selector, QString code) {
if (_record->isChecked())
appendCommand("do "+map(selector)+"\n "
+map(code).replace("\n", "\n "));
}
void cleanup(const QString& selector) {
QString text(_testscript->toPlainText());
QStringList lines(text.split("\n"));
bool changed(false);
while (lines.size() &&
(lines.last()=="click "+selector ||
lines.last().startsWith("setvalue "+selector+" -> "))) {
lines.removeLast();
changed = true;
}
if (changed) {
_testscript->setPlainText(lines.join("\n"));
_testscript->moveCursor(QTextCursor::End);
_testscript->ensureCursorVisible();
}
}
void setValue(const QString& selector, QString code) {
if (_record->isChecked()) {
cleanup(selector);
appendCommand("setvalue "+map(selector)+" -> '"
appendCommand(selector,
"setvalue "+map(selector)+" -> '"
+map(code).replace("'", "\\'").replace("\n", "\\n")+"'");
}
}
void setValue(const QString& selector, QStringList code) {
if (_record->isChecked()) {
cleanup(selector);
appendCommand("setvalue "+map(selector)+" -> '"+
appendCommand(selector,
"setvalue "+map(selector)+" -> '"+
map(code.replaceInStrings("'", "\\'")
.replaceInStrings("\n", "\\n")
.join("', '")+"'"));
}
void appendCommand(const QString& txt) {
for (auto testscript: _testscripts) testscript->appendCommand(txt);
}
void appendCommand(const QString& selector, const QString& txt) {
for (auto testscript: _testscripts) testscript->appendCommand(selector, txt);
}
bool handleMooTools(QWebElement element) {
QString selected(selector(element));
@ -697,8 +560,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
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=\""
appendCommand("click realmouse "+map("li.active-result[data-option-array-index=\""
+element.attribute("data-option-array-index")
+"\"]"));
appendCommand("sleep "+map("1"));
@ -942,12 +804,6 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI {
URL_VIEW = 0,
PROGRESS_VIEW
};
enum RunStatus {
STATUS_NONE = 0,
STATUS_RUNNING,
STATUS_SUCCESS,
STATUS_ERROR
};
private:
QString _filename;
QWebElement _lastFocused; // cache for last focussed element

@ -10,6 +10,9 @@
<height>1180</height>
</rect>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="windowTitle">
<string/>
</property>
@ -101,7 +104,7 @@
<x>0</x>
<y>0</y>
<width>888</width>
<height>30</height>
<height>34</height>
</rect>
</property>
<widget class="QMenu" name="menuViews">
@ -115,14 +118,13 @@
</property>
<addaction name="_actionOpen"/>
<addaction name="_actionOpenSetupScript"/>
<addaction name="separator"/>
<addaction name="_actionSave"/>
<addaction name="_actionSaveAs"/>
<addaction name="separator"/>
<addaction name="_actionRun"/>
<addaction name="_actionRunLine"/>
<addaction name="separator"/>
<addaction name="_actionRevertToSaved"/>
<addaction name="_actionClear"/>
<addaction name="_actionRun"/>
<addaction name="separator"/>
<addaction name="_actionQuit"/>
</widget>
<widget class="QMenu" name="menuHelp">
@ -138,7 +140,7 @@
<widget class="QStatusBar" name="statusbar"/>
<widget class="QDockWidget" name="_domDock">
<property name="windowTitle">
<string>D&amp;OM Tree</string>
<string>DOM &amp;Tree</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
@ -393,144 +395,6 @@ this.dispatchEvent(evObj);</string>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="_scriptDock">
<property name="windowTitle">
<string>&amp;Test Script</string>
</property>
<attribute name="dockWidgetArea">
<number>4</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_12">
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="CodeEditor" name="_testscript"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QPushButton" name="_record">
<property name="text">
<string>Record</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_run">
<property name="text">
<string>Run</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="_screenshots">
<property name="text">
<string>Screenshots</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>28</height>
</size>
</property>
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QStackedWidget" name="_status">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_6"/>
<widget class="QWidget" name="page_5">
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;div style=&quot;font-size: xx-large&quot;&gt;⌛&lt;/div&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;div style=&quot;font-size: xx-large; color: green&quot;&gt;✔&lt;/div&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_4">
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;div style=&quot;font-size: xx-large; color: red&quot;&gt;✘&lt;/div&gt;</string>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QProgressBar" name="_progress">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="_logDock">
<property name="windowTitle">
<string>Scri&amp;pt Run Log</string>
@ -699,6 +563,9 @@ this.dispatchEvent(evObj);</string>
</property>
</action>
<action name="_actionSaveAs">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save &amp;As ...</string>
</property>
@ -715,6 +582,9 @@ this.dispatchEvent(evObj);</string>
</property>
</action>
<action name="_actionRun">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Run</string>
</property>
@ -728,6 +598,9 @@ this.dispatchEvent(evObj);</string>
</property>
</action>
<action name="_actionClear">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Clear</string>
</property>
@ -986,38 +859,6 @@ this.dispatchEvent(evObj);</string>
</hint>
</hints>
</connection>
<connection>
<sender>_actionTestScript</sender>
<signal>triggered(bool)</signal>
<receiver>_scriptDock</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>443</x>
<y>155</y>
</hint>
</hints>
</connection>
<connection>
<sender>_scriptDock</sender>
<signal>visibilityChanged(bool)</signal>
<receiver>_actionTestScript</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>443</x>
<y>155</y>
</hint>
<hint type="destinationlabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>_actionLog</sender>
<signal>triggered(bool)</signal>
@ -1066,22 +907,6 @@ this.dispatchEvent(evObj);</string>
</hint>
</hints>
</connection>
<connection>
<sender>_actionRun</sender>
<signal>triggered()</signal>
<receiver>_run</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>299</x>
<y>90</y>
</hint>
</hints>
</connection>
<connection>
<sender>_url</sender>
<signal>activated(int)</signal>
@ -1098,21 +923,5 @@ this.dispatchEvent(evObj);</string>
</hint>
</hints>
</connection>
<connection>
<sender>_testscript</sender>
<signal>modificationChanged(bool)</signal>
<receiver>TestGUI</receiver>
<slot>setWindowModified(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>126</x>
<y>144</y>
</hint>
<hint type="destinationlabel">
<x>443</x>
<y>589</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -4,22 +4,22 @@
#include <version.hxx>
int main(int argc, char *argv[]) try {
QApplication a(argc, argv);
a.setApplicationDisplayName(a.tr("WebTester"));
a.setApplicationName(webtester::package_name().c_str());
a.setApplicationVersion(webtester::version().c_str());
QApplication app(argc, argv);
app.setApplicationDisplayName(app.tr("WebTester"));
app.setApplicationName(webtester::package_name().c_str());
app.setApplicationVersion(webtester::version().c_str());
QCommandLineParser parser;
parser.addHelpOption();
parser.addOption(QCommandLineOption
(QStringList()<<"u"<<"url",
"set initial URL to <url>", "url"));
parser.process(a);
parser.process(app);
QStringList scripts(parser.positionalArguments());
TestGUI w(0, parser.value("url"),
TestGUI win(0, parser.value("url"),
scripts.size()>1?scripts[0]:"",
scripts.size()>1?scripts[1]:scripts.size()?scripts[0]:"");
w.show();
return a.exec();
win.show();
return app.exec();
} catch (std::exception &x) {
std::cerr<<"**** error: "<<x.what()<<std::endl;
return 1;

Loading…
Cancel
Save