Test your websites with this simple GUI based scripted webtester. Generate simple testscripts directly from surfng on the webpage, enhance them with your commands, with variables, loops, checks, … and finally run automated web tests.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
9.1 KiB
248 lines
9.1 KiB
#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 = 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(); |
|
_lineBar->hide(); |
|
_progress->hide(); |
|
_status->setCurrentIndex(STATUS_NONE); |
|
} |
|
CodeEditor* editor() { |
|
return _editor; |
|
} |
|
QString name() { |
|
return _name; |
|
} |
|
void name(QString name) { |
|
_name = name; |
|
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; |
|
}; |
|
|
|
#endif
|
|
|