#ifndef __SCRIPTFILE__HXX #define __SCRIPTFILE__HXX #include #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&); 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 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); } 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