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.

241 lines
8.7 KiB

9 years ago
#include <commands.hxx>
#include <webpage.hxx>
#include <QApplication>
#include <QFile>
#include <QWebView>
#include <QStringList>
#include <QCommandLineParser>
#include <iostream>
#include <sstream>
#include <fstream>
#include <QDateTime>
#include <xml-cxx/xml.hxx>
#include <mrw/string.hxx>
9 years ago
#include <version.hxx>
using namespace NAMESPACE;
9 years ago
/** @page junitXml Test Output XML Format
The test output XML format emulates JUnit's format, so that JUnit
tools can be used to evaluate ist, namely on tools like jenkins.
@code{.xml}
<testsuites>
<testsuite name="..." timestamp="..." failures="...">
<testcase classname="..." name="...">
<failure message="..." />
<system-out>...</system-out>
<system-err>...</system-err>
</testcase>
<testcase ... />
</testsuite>
<testsuite ...>
...
</testsuite>
<testsuites>
@endcode
The default name for a testsuite is the name of the test script
file. it can be overwritten using the @c testsuite statement. The
attribute @c failures contains the number of failed test cases.
The default classname for a testcase is @c testsuite-preparation
before the script runs, then in the script it is the name of the
current command and the classname equals to the testsuite's name.
If you specify testsuites, testcases in your script file, then
this overwrites the defaults. Here @c testsuite maps to the
testsuite's name, @c testcase maps to the next testcase's
classname.
*/
QString format(QString txt, int indent = 2, int cpl = 80) {
9 years ago
QStringList res;
QStringList lines(txt.split('\n'));
QString ind(indent, ' ');
for (QString line: lines) {
9 years ago
line.insert(0, ind);
for (int pos(indent); line.size()-pos>cpl; ++pos) {
int pos2=line.lastIndexOf(' ', pos+cpl);
if (pos2>pos) pos=pos2;
else pos=line.indexOf(' ', pos+1);
if (pos<0) break;
9 years ago
line.remove(pos, 1);
line.insert(pos, "\n"+ind);
}
res+=line;
}
return res.join('\n');
}
9 years ago
QString format(std::string txt, int indent = 2, int cpl = 60) {
return format(QString::fromStdString(txt), indent, cpl);
}
9 years ago
QString help(const Script& s) {
std::ostringstream ss;
ss<<"Synopsis"<<std::endl
<<std::endl
<<" webrunner [<OPTIONS>] [<file1> [<file2> [...]]]"<<std::endl
<<std::endl
9 years ago
<<"DESCRIPTION"<<std::endl
<<std::endl
<<format(description()).toStdString()<<std::endl
<<std::endl
<<format(readme()).toStdString()<<std::endl
<<std::endl
9 years ago
<<"SCRIPT SYNTAX"<<std::endl
9 years ago
<<std::endl
<<format(s.syntax()).toStdString()<<std::endl
<<std::endl
9 years ago
<<"SCRIPT COMMANDS"<<std::endl
9 years ago
<<std::endl
<<format(s.commands()).toStdString()<<std::endl;
return QString::fromStdString(ss.str());
}
int main(int argc, char *argv[]) try {
bool failed(false);
QApplication a(argc, argv);
QWebView p;
p.setPage(new TestWebPage(&p, true));
QCommandLineParser parser;
Script script;
parser.setApplicationDescription(help(script));
parser.addHelpOption();
parser.addOption(QCommandLineOption
(QStringList()<<"p"<<"path",
"search <path> within the test scripts", "path"));
9 years ago
parser.addOption(QCommandLineOption
(QStringList()<<"x"<<"xml",
"store XML output in <xmlfile>", "xmlfile"));
parser.addOption(QCommandLineOption
(QStringList()<<"r"<<"retries",
"on error retry up to <maxretries> times",
"maxretries", "0"));
parser.addOption(QCommandLineOption
(QStringList()<<"timeout",
"set default timeout in seconds",
"timeout", "40"));
9 years ago
parser.addOption(QCommandLineOption
(QStringList()<<"W"<<"width",
"set screenshot size to <width> pixel", "width", "2048"));
parser.addOption(QCommandLineOption
(QStringList()<<"H"<<"height",
"set screenshot size to <height> pixel", "height", "2048"));
parser.addOption(QCommandLineOption
(QStringList()<<"v"<<"version",
"show version information"));
parser.addOption(QCommandLineOption
(QStringList()<<"s"<<"skipped",
"treat skipped test cases as failure in XML output file"));
parser.addOption(QCommandLineOption
(QStringList()<<"t"<<"target-path",
"set screenshot target path to <path>", "path",
QDir().absolutePath()+QDir::separator()+"attachments"));
parser.process(a);
if (parser.isSet("version")) {
9 years ago
std::cout<<*argv<<std::endl
<<" from package "<<package_string()<<std::endl
<<" by "<<author()<<std::endl
<<" built on "<<build_date()<<std::endl;
9 years ago
return 0;
}
if (parser.isSet("path")) script.path(parser.value("path"));
9 years ago
int retries(parser.value("retries").toInt());
int timeout(parser.value("timeout").toInt());
9 years ago
int width(parser.value("width").toInt());
int height(parser.value("height").toInt());
script.defaultTimeout(parser.value("timeout").toInt());
9 years ago
QString target(parser.value("target-path"));
p.resize(width, height);
std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuites"));
for (QString file: parser.positionalArguments()) {
9 years ago
int expectedtestcases(-1);
xml::Node testsuite("testsuite");
testsuite.attr("name") = QFileInfo(file).baseName().toStdString();
testsuite.attr("timestamp") =
QDateTime::currentDateTime().toString(Qt::ISODate).toStdString();
xml::Node testcase("testcase");
testcase.attr("classname") = "testsuite-preparation";
try {
script.reset();
script.log("=====================");
script.log("TEST: "+file);
script.log("---------------------");
testcase.attr("name") = "open test suite file";
testsuite<<testcase;
QFile f(file);
if (!f.open(QIODevice::ReadOnly))
throw std::runtime_error("cannot open file "+file.toStdString());
QString txt(QString::fromUtf8(f.readAll()));
testcase.attr("name") = "parse test suite file";
testsuite<<testcase;
script.parse(txt.split('\n'), file);
expectedtestcases = script.countSteps()+2;
9 years ago
if (failed) {
script.log("FAILED: "+file+" skipped due to previous error");
testcase.attr("name") = "testsuite";
xml::Node failure("failure");
failure.attr("message") = "ignored due to previous failure";
testsuite<<(testcase<<failure);
testsuite.attr("failures") =
mrw::string(expectedtestcases+1-testsuite.children());
if (parser.isSet("skipped")) {
testcase.attr("name") = "skipped test case";
failure.attr("message") = "skipped due to previous failure";
testcase<<failure;
for (int i(testsuite.children()); i<expectedtestcases; ++i)
testsuite<<testcase;
}
} else {
testsuite.attr("failures") = "0";
(*testsuites)<<testsuite;
script.run(p.page()->mainFrame(), testsuites,
9 years ago
target+QDir::separator()+QFileInfo(file).baseName(),
true, retries);
script.log("SUCCESS: "+file);
}
} catch (std::exception& e) {
failed = true;
9 years ago
script.log("FAILED: "+file+" with "+e.what());
xml::Node failure("failure");
failure.attr("message") = Script::xmlattr(e.what()).toStdString();
testsuites->last().last()<<failure;
9 years ago
if (expectedtestcases==-1)
testsuites->last().attr("failures") = "1";
9 years ago
else
testsuites->last().attr("failures") =
mrw::string(expectedtestcases+1-testsuites->last().children());
9 years ago
if (parser.isSet("skipped")) {
testcase.attr("name") = "skipped test case";
failure.attr("message") = "skipped due to previous failure";
testcase<<failure;
for (int i(testsuites->last().children()); i<expectedtestcases; ++i)
testsuites->last()<<testcase;
9 years ago
}
}
if (expectedtestcases==-1) {
testsuites->last().attr("tests") =
mrw::string(testsuites->last().children());
9 years ago
} else {
testsuites->last().attr("tests") = mrw::string(expectedtestcases);
9 years ago
}
if (script.cout().size())
testsuites->last()<<(xml::String("system-out") =
script.xmlattr(script.cout()).toStdString());
9 years ago
if (script.cerr().size())
testsuites->last()<<(xml::String("system-err") =
script.xmlattr(script.cerr()).toStdString());
9 years ago
}
if (parser.isSet("xml")) { // todo: write xml file
std::ofstream xmlfile(parser.value("xml").toStdString());
xmlfile<<(*testsuites)<<std::endl;
9 years ago
}
return failed ? 1 : 0;
} catch (std::exception& e) {
return 1;
}