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.
 
 
 
 
 
 

303 lines
11 KiB

#ifndef __SCRIPTFILE__HXX
#define __SCRIPTFILE__HXX
#include <commands.hxx>
#include <ui_scriptfile.hxx>
#include <QMessageBox>
#include <QScrollBar>
#include <QShortcut>
#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&);
void progress(QString, int, int, int);
public:
ScriptFile(QWidget* p = nullptr, QString name = QString()): QDockWidget(p), _name(name) {
setupUi(this);
setWindowTitle(_name+"[*]");
assert(connect(_editor, SIGNAL(textChanged()), SLOT(modified())));
assert(connect(_editor, SIGNAL(include(QString)), SIGNAL(include(QString))));
assert(connect(_editor, SIGNAL(link(QString)), SIGNAL(link(QString))));
assert(connect(_editor, SIGNAL(cursorPositionChanged()), SLOT(setLine())));
assert(connect(_line, SIGNAL(valueChanged(int)), SLOT(gotoLine(int))));
_searchBar->hide();
_replaceBar->hide();
_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 setLine() {
int pos(_editor->textCursor().blockNumber()+1);
if (pos) _line->setValue(pos);
}
void gotoLine(int line) {
_editor->gotoLine(line);
}
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() {
_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, int)),
SIGNAL(progress(QString, int, int, int))));
QString text(_editor->textCursor().selection().toPlainText());
if (text.isEmpty()) text = _editor->toPlainText();
run(_name, text, _screenshots->isChecked(), script);
_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);
}
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 runEnabled(bool f = true) {
_run->setEnabled(f);
}
void on__run_clicked() {
run();
}
void on__from_textEdited(const QString& txt) {
on__next_clicked();
}
void on__next_clicked() {
if (_regex->isChecked())
_editor->find(QRegExp(_from->text()));
else
_editor->find(_from->text());
}
void on__previous_clicked() {
if (_regex->isChecked())
_editor->find(QRegExp(_from->text()), QTextDocument::FindBackward);
else
_editor->find(_from->text(), QTextDocument::FindBackward);
}
void on__replace_clicked() {
QTextCursor tc(_editor->textCursor());
if (tc.hasSelection()) tc.insertText(_to->text());
}
void on__replaceAll_clicked() {
QTextCursor cursor(_editor->textCursor());
if (_regex->isChecked())
_editor->setPlainText(_editor->toPlainText().replace(QRegExp(_from->text()), _to->text()));
else
_editor->setPlainText(_editor->toPlainText().replace(_from->text(), _to->text()));
_editor->setTextCursor(cursor);
}
void startSearch() {
_searchBar->show();
_from->setFocus();
_from->selectAll();
}
void startReplace() {
_searchBar->show();
_replaceBar->show();
_to->setFocus();
_to->selectAll();
}
protected:
void closeEvent(QCloseEvent*) {
close(this);
}
void keyPressEvent(QKeyEvent *e) {
if (e->matches(QKeySequence::Find))
startSearch();
if (e->matches(QKeySequence::Replace))
startReplace();
if (e->matches(QKeySequence::FindNext))
on__next_clicked();
if (e->matches(QKeySequence::FindPrevious))
on__previous_clicked();
if (e->matches(QKeySequence::Cancel)) {
_searchBar->hide();
_replaceBar->hide();
}
return QDockWidget::keyPressEvent(e);
}
private:
enum RunStatus {
STATUS_NONE = 0,
STATUS_RUNNING,
STATUS_SUCCESS,
STATUS_ERROR
};
private:
QString _name;
};
#endif