diff --git a/src/scriptfile.hxx b/src/scriptfile.hxx index 395a3c2..782d75d 100644 --- a/src/scriptfile.hxx +++ b/src/scriptfile.hxx @@ -1,23 +1,32 @@ #ifndef __SCRIPTFILE__HXX #define __SCRIPTFILE__HXX +#include #include +#include +#include +#include #include 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 cmd(script.command()); + if (cmd) + QMessageBox::critical(this, + tr("Test Failed"), + tr("" + "

Error [%1]

" + "
" + "
Command:
%3
" + "
File:
%4
" + "
Line:
%5
" + "
Error Message:
%2
" + "
" + "") + .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("" + "

Error [%1]

" + "

%2

" + "") + .arg(demangle(typeid(x).name())) + .arg(QString(x.what()).replace("\n", "
"))); + } + _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*) { + void closeEvent(QCloseEvent*) { close(this); } + private: + enum RunStatus { + STATUS_NONE = 0, + STATUS_RUNNING, + STATUS_SUCCESS, + STATUS_ERROR + }; private: QString _name; }; diff --git a/src/scriptfile.ui b/src/scriptfile.ui index f40d278..961d008 100644 --- a/src/scriptfile.ui +++ b/src/scriptfile.ui @@ -7,16 +7,135 @@ 0 0 628 - 378 + 593 - DockW&idget + Do&ckWidget - + - + + + + + + + + + + + + + Record + + + true + + + false + + + + + + + Run + + + + + + + Screenshots + + + + + + + Qt::Vertical + + + + 20 + 28 + + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + + 0 + 0 + + + + <div style="font-size: xx-large">⌛</div> + + + + + + + + + + + + 0 + 0 + + + + <div style="font-size: xx-large; color: green">✔</div> + + + + + + + + + + + + 0 + 0 + + + + <div style="font-size: xx-large; color: red">✘</div> + + + true + + + + + + + + + + @@ -72,21 +191,28 @@ - + - + - + - page + line + + + + 24 + + + diff --git a/src/testgui.hxx b/src/testgui.hxx index 7be3e0e..948d014 100644 --- a/src/testgui.hxx +++ b/src/testgui.hxx @@ -1,7 +1,7 @@ /*! @file @id $Id$ -*/ + */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 #ifndef TESTGUI_HXX @@ -24,7 +24,6 @@ #include #include #include -#include #include 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,22 +62,22 @@ 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())); + appendCommand("load "+map(_url->currentText())); storeUrl(_url->currentText()); _webprogress->setFormat(_url->currentText()); _web->load(_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 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(); - } - 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 cmd(script.command()); - if (cmd) - QMessageBox::critical(this, - tr("Test Failed"), - tr("" - "

Error [%1]

" - "
" - "
Command:
%3
" - "
File:
%4
" - "
Line:
%5
" - "
Error Message:
%2
" - "
" - "") - .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("" - "

Error [%1]

" - "

%2

" - "") - .arg(demangle(typeid(x).name())) - .arg(QString(x.what()).replace("\n", "
"))); - } - _run->setEnabled(true); - _record->setEnabled(true); - _record->setChecked(oldRecordState); - _progress->hide(); + ScriptFile* active(activeScriptFile()); + if (active) active->clear(); + } + void on__actionRun_triggered() { + ScriptFile* active(activeScriptFile()); + if (active) active->run(); } void on__focused_clicked() { enterText(true); @@ -228,8 +137,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI { } void on__web_linkClicked(const QUrl& url) { enterText(true); - if (_record->isChecked()) - appendCommand("load "+map(url.url())); + appendCommand("load "+map(url.url())); } void on__web_loadProgress(int progress) { enterText(true); @@ -237,8 +145,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI { } void on__web_loadStarted() { enterText(true); - if (_record->isChecked()) - appendCommand("expect "+map("loadStarted")); + appendCommand("expect "+map("loadStarted")); _webprogress->setValue(0); _urlStack->setCurrentIndex(PROGRESS_VIEW); } @@ -252,8 +159,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI { _webprogress->setFormat(url.url()); storeUrl(url); enterText(true); - if (_record->isChecked()) - appendCommand("expect "+map("urlChanged "+url.url())); + appendCommand("expect "+map("urlChanged "+url.url())); } void on__web_selectionChanged() { _source->setPlainText(_web->hasSelection() @@ -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,32 +206,13 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI { } void uploadFile(QString filename) { enterText(true); - if (_record->isChecked()) - appendCommand("upload "+map(filename)); + 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"); + appendCommand("download2"); } void logging(const QString& txt) { _log->appendPlainText(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(w)); - // w=qobject_cast(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 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(wid); + return active; + } void closeEvent(QCloseEvent* event) { QSettings settings("mrw", "webtester"); settings.setValue("geometry", saveGeometry()); @@ -440,41 +340,39 @@ 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)) { - // handled in handleMooTools - } else if (_lastFocused.tagName()=="SELECT") { - // click on a select results in a value change - // find all selected options ... - QStringList v; - for (QWebElement option: _lastFocused.findAll("option")) { - //! @bug QT does not support selected - if (option.evaluateJavaScript("this.selected").toBool()) - v += value(option); - } - setValue(selected, v); - } else if (_lastFocused.tagName()=="TEXTAREA" || - (_lastFocused.tagName()=="INPUT" && - _lastFocused.attribute("type")=="text")) { - // user clickt in a text edit field, so not the klick - // is important, but the text that will be typed - _typing = true; - } else { - if (_web->page()->selectedText() != "") { - // user has selected a text, append a check - appendCommand("exists "+map(selected) - +" -> "+_web->page()->selectedText()); - _web->page()->findText(QString()); - } else { - // user has clicked without selection, append a click - appendCommand("click "+map(selected)); - } + if (!element.isNull()) { + QString selected(selector(element)); + if (handleMooTools(_lastFocused)) { + // handled in handleMooTools + } else if (_lastFocused.tagName()=="SELECT") { + // click on a select results in a value change + // find all selected options ... + QStringList v; + for (QWebElement option: _lastFocused.findAll("option")) { + //! @bug QT does not support selected + if (option.evaluateJavaScript("this.selected").toBool()) + v += value(option); } + setValue(selected, v); + } else if (_lastFocused.tagName()=="TEXTAREA" || + (_lastFocused.tagName()=="INPUT" && + _lastFocused.attribute("type")=="text")) { + // user clickt in a text edit field, so not the klick + // is important, but the text that will be typed + _typing = true; } else { - appendCommand("# click, but where?"); + if (_web->page()->selectedText() != "") { + // user has selected a text, append a check + appendCommand("exists "+map(selected) + +" -> "+_web->page()->selectedText()); + _web->page()->findText(QString()); + } else { + // user has clicked without selection, append a click + appendCommand("click "+map(selected)); + } } + } else { + appendCommand("# click, but where?"); } } break; case QEvent::MouseButtonPress: { @@ -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 { @@ -535,10 +413,10 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI { throw std::runtime_error("file read failed"); on__setupscript_textChanged(); } 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())); + 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 enterText(bool force=false) { @@ -561,9 +439,9 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI { return QString(); // error } void highlight(QWebElement element) { - element - .evaluateJavaScript("var selection = window.getSelection();" - "selection.setBaseAndExtent(this, 0, this, 1);"); + element + .evaluateJavaScript("var selection = window.getSelection();" + "selection.setBaseAndExtent(this, 0, this, 1);"); } QWebElement focused(QMouseEvent* event = 0) { for (QWebElement element: _web->page()->currentFrame()->findAllElements("*")) { @@ -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(); - } + appendCommand("do "+map(selector)+"\n " + +map(code).replace("\n", "\n ")); } void setValue(const QString& selector, QString code) { - if (_record->isChecked()) { - cleanup(selector); - appendCommand("setvalue "+map(selector)+" -> '" - +map(code).replace("'", "\\'").replace("\n", "\\n")+"'"); - } + 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)+" -> '"+ - map(code.replaceInStrings("'", "\\'") - .replaceInStrings("\n", "\\n") - .join("', '")+"'")); - } + 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,10 +560,9 @@ 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=\"" - +element.attribute("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")); return true; } else if (element.tagName()=="INPUT") { @@ -861,7 +723,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI { case QEvent::LayoutDirectionChange: return "QEvent::LayoutDirectionChange - The direction of layouts changed."; case QEvent::LayoutRequest: return "QEvent::LayoutRequest - Widget layout needs to be redone."; case QEvent::Leave: return "QEvent::Leave - Mouse leaves widget's boundaries."; - //case QEvent::LeaveEditFocus: return "QEvent::LeaveEditFocus - An editor widget loses focus for editing. QT_KEYPAD_NAVIGATION must be defined."; + //case QEvent::LeaveEditFocus: return "QEvent::LeaveEditFocus - An editor widget loses focus for editing. QT_KEYPAD_NAVIGATION must be defined."; case QEvent::LeaveWhatsThisMode: return "QEvent::LeaveWhatsThisMode - Send to toplevel widgets when the application leaves \"What's This?\" mode."; case QEvent::LocaleChange: return "QEvent::LocaleChange - The system locale has changed."; case QEvent::NonClientAreaMouseButtonDblClick: return "QEvent::NonClientAreaMouseButtonDblClick - A mouse double click occurred outside the client area."; @@ -887,7 +749,7 @@ class TestGUI: public QMainWindow, protected Ui::TestGUI { case QEvent::Polish: return "QEvent::Polish - The widget is polished."; case QEvent::PolishRequest: return "QEvent::PolishRequest - The widget should be polished."; case QEvent::QueryWhatsThis: return "QEvent::QueryWhatsThis - The widget should accept the event if it has \"What's This?\" help."; - //case QEvent::ReadOnlyChange: return "QEvent::ReadOnlyChange - Widget's read-only state has changed (since Qt 5.4)."; + //case QEvent::ReadOnlyChange: return "QEvent::ReadOnlyChange - Widget's read-only state has changed (since Qt 5.4)."; case QEvent::RequestSoftwareInputPanel: return "QEvent::RequestSoftwareInputPanel - A widget wants to open a software input panel (SIP)."; case QEvent::Resize: return "QEvent::Resize - Widget's size changed (QResizeEvent)."; case QEvent::ScrollPrepare: return "QEvent::ScrollPrepare - The object needs to fill in its geometry information (QScrollPrepareEvent)."; @@ -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 diff --git a/src/testgui.ui b/src/testgui.ui index 2bdf052..53db2a8 100644 --- a/src/testgui.ui +++ b/src/testgui.ui @@ -10,6 +10,9 @@ 1180 + + Qt::NoFocus + @@ -101,7 +104,7 @@ 0 0 888 - 30 + 34 @@ -115,14 +118,13 @@ + - - - - + + @@ -138,7 +140,7 @@ - D&OM Tree + DOM &Tree 2 @@ -393,144 +395,6 @@ this.dispatchEvent(evObj); - - - &Test Script - - - 4 - - - - - - - - - - - - - - Record - - - true - - - false - - - - - - - Run - - - - - - - Screenshots - - - - - - - Qt::Vertical - - - - 20 - 28 - - - - - - - - - 0 - 0 - - - - 0 - - - - - - - - - 0 - 0 - - - - <div style="font-size: xx-large">⌛</div> - - - - - - - - - - - - 0 - 0 - - - - <div style="font-size: xx-large; color: green">✔</div> - - - - - - - - - - - - 0 - 0 - - - - <div style="font-size: xx-large; color: red">✘</div> - - - true - - - - - - - - - - - - - - - 24 - - - - - - Scri&pt Run Log @@ -699,6 +563,9 @@ this.dispatchEvent(evObj); + + false + Save &As ... @@ -715,6 +582,9 @@ this.dispatchEvent(evObj); + + false + &Run @@ -728,6 +598,9 @@ this.dispatchEvent(evObj); + + false + &Clear @@ -986,38 +859,6 @@ this.dispatchEvent(evObj); - - _actionTestScript - triggered(bool) - _scriptDock - setVisible(bool) - - - -1 - -1 - - - 443 - 155 - - - - - _scriptDock - visibilityChanged(bool) - _actionTestScript - setChecked(bool) - - - 443 - 155 - - - -1 - -1 - - - _actionLog triggered(bool) @@ -1066,22 +907,6 @@ this.dispatchEvent(evObj); - - _actionRun - triggered() - _run - click() - - - -1 - -1 - - - 299 - 90 - - - _url activated(int) @@ -1098,21 +923,5 @@ this.dispatchEvent(evObj); - - _testscript - modificationChanged(bool) - TestGUI - setWindowModified(bool) - - - 126 - 144 - - - 443 - 589 - - - diff --git a/src/webtester.cxx b/src/webtester.cxx index 7232d23..6c39beb 100644 --- a/src/webtester.cxx +++ b/src/webtester.cxx @@ -4,22 +4,22 @@ #include 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")); - parser.process(a); + parser.process(app); QStringList scripts(parser.positionalArguments()); - TestGUI w(0, parser.value("url"), - scripts.size()>1?scripts[0]:"", - scripts.size()>1?scripts[1]:scripts.size()?scripts[0]:""); - w.show(); - return a.exec(); + TestGUI win(0, parser.value("url"), + scripts.size()>1?scripts[0]:"", + scripts.size()>1?scripts[1]:scripts.size()?scripts[0]:""); + win.show(); + return app.exec(); } catch (std::exception &x) { std::cerr<<"**** error: "<