|
|
|
@ -1,7 +1,7 @@ |
|
|
|
|
/*! @file
|
|
|
|
|
|
|
|
|
|
@id $Id$ |
|
|
|
|
*/ |
|
|
|
|
*/ |
|
|
|
|
// 1 2 3 4 5 6 7 8
|
|
|
|
|
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
|
|
|
|
#ifndef TESTGUI_HXX |
|
|
|
@ -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,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<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(); |
|
|
|
|
} |
|
|
|
|
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(); |
|
|
|
|
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<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,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
|
|
|
|
|