/*! @file
@ id $ Id $
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
# ifndef COMMANDS_HXX
# define COMMANDS_HXX
# include <exceptions.hxx>
# include <webpage.hxx>
# include <QNetworkReply>
# include <QCoreApplication>
# include <QStringList>
# include <QWebFrame>
# include <QWebView>
# include <QWebElement>
# include <QPainter>
# include <QImage>
# include <QSslKey>
# include <QTimer>
# include <QProcess>
# include <QMouseEvent>
# include <QRegularExpression>
# include <QNetworkCookieJar>
# include <QNetworkCookie>
# include <vector>
# include <queue>
# include <map>
# include <memory>
# include <sstream>
# include <cassert>
# include <xml-cxx/xml.hxx>
namespace std {
template < typename T > class optional {
private :
T * _opt ;
bool _set ;
public :
optional ( ) :
_opt ( 0 ) , _set ( false ) {
}
optional ( const T & other ) :
_opt ( new T ( other ) ) , _set ( true ) {
}
~ optional ( ) {
if ( _set ) delete _opt ;
}
optional & operator = ( const T & other ) {
if ( _set ) delete _opt ;
_set = true ;
_opt = new T ( other ) ;
return * this ;
}
T * operator - > ( ) {
return _opt ;
}
T & operator * ( ) {
return * _opt ;
}
const T * operator - > ( ) const {
return _opt ;
}
const T & operator * ( ) const {
return * _opt ;
}
operator bool ( ) const {
return _set ;
}
} ;
}
class Script ;
class Command ;
class Function ;
class Logger {
public :
Logger ( Command * command , Script * script , bool showLines = true ) ;
void operator [ ] ( QString txt ) ;
void operator ( ) ( QString txt ) ;
~ Logger ( ) ;
private :
Command * _command ;
Command * _previous ;
Script * _script ;
} ;
class Command : public QObject {
Q_OBJECT ;
public :
Command ( ) : _log ( true ) , _line ( - 1 ) , _indent ( 0 ) { }
virtual ~ Command ( ) { }
virtual QString tag ( ) const = 0 ;
virtual QString description ( ) const = 0 ;
virtual QString command ( ) const = 0 ;
virtual std : : shared_ptr < Command > parse ( Script * , QString ,
QStringList & , QString , int , int ) = 0 ;
virtual bool execute ( Script * , QWebFrame * ) = 0 ;
static void error ( Logger & log , const Exception & e ) {
log ( QString ( " FAILED: " ) + e . what ( ) ) ;
throw e ;
}
void line ( int linenr ) {
_line = linenr ;
}
int line ( ) const {
return _line ;
}
void file ( QString filename ) {
_file = filename ;
}
QString file ( ) const {
return _file ;
}
void indent ( int i ) {
_indent = i ;
}
int indent ( ) const {
return _indent ;
}
bool log ( ) {
return _log ;
}
void log ( bool l ) {
_log = l ;
}
QString result ( ) {
return _result ;
}
virtual bool isTestcase ( ) {
return true ;
}
static void realMouseClick ( Logger & log , QWebFrame * frame , QString selector ) {
QWebElement element ( find ( frame , selector ) ) ;
if ( element . isNull ( ) ) error ( log , ElementNotFound ( selector ) ) ;
realMouseClick ( element ) ;
}
static void realMouseClick ( const QWebElement & element ) {
QWidget * web ( element . webFrame ( ) - > page ( ) - > view ( ) ) ;
QRect elGeom = element . geometry ( ) ;
QPoint elPoint = elGeom . center ( ) ;
int elX = elPoint . x ( ) ;
int elY = elPoint . y ( ) ;
int webWidth = web - > width ( ) ;
int webHeight = web - > height ( ) ;
int pixelsToScrolRight = 0 ;
int pixelsToScrolDown = 0 ;
if ( elX > webWidth )
pixelsToScrolRight = //the +10 scrolls a bit further
elX - webWidth + elGeom . width ( ) / 2 + 10 ;
if ( elY > webHeight )
pixelsToScrolDown = //the +10 scrolls a bit further
elY - webHeight + elGeom . height ( ) / 2 + 10 ;
element . webFrame ( ) - > setScrollBarValue ( Qt : : Horizontal , pixelsToScrolRight ) ;
element . webFrame ( ) - > setScrollBarValue ( Qt : : Vertical , pixelsToScrolDown ) ;
QPoint pointToClick ( elX - pixelsToScrolRight , elY - pixelsToScrolDown ) ;
QMouseEvent pressEvent ( QMouseEvent : : MouseButtonPress ,
pointToClick , Qt : : LeftButton , Qt : : LeftButton ,
Qt : : NoModifier ) ;
QCoreApplication : : sendEvent ( web , & pressEvent ) ;
QMouseEvent releaseEvent ( QMouseEvent : : MouseButtonRelease ,
pointToClick , Qt : : LeftButton , Qt : : LeftButton ,
Qt : : NoModifier ) ;
QCoreApplication : : sendEvent ( web , & releaseEvent ) ;
QCoreApplication : : processEvents ( ) ;
}
static void sleep ( int s ) {
QTime dieTime = QTime : : currentTime ( ) . addSecs ( s ) ;
while ( QTime : : currentTime ( ) < dieTime )
QCoreApplication : : processEvents ( QEventLoop : : AllEvents , 100 ) ;
}
protected :
bool runScript ( Logger & log , Command * parentCommand ,
std : : shared_ptr < Script > script ,
Script * parent , QWebFrame * frame ,
QStringList vars = QStringList ( ) ,
QStringList args = QStringList ( ) ) ;
QStringList subCommandBlock ( QStringList & in ) {
QStringList commands ;
int pos ( - 1 ) ;
while ( in . size ( ) & & in [ 0 ] . size ( ) & & in [ 0 ] [ 0 ] = = ' '
& & pos < = ( signed ) in [ 0 ] . toStdString ( ) . find_first_not_of ( ' ' ) ) {
if ( pos < 0 ) pos = in [ 0 ] . toStdString ( ) . find_first_not_of ( ' ' ) ;
commands + = in . takeFirst ( ) . mid ( pos ) ;
}
return commands ;
}
QStringList commaSeparatedList ( QString value ) {
value = value . trimmed ( ) ;
if ( ! value . size ( ) ) return QStringList ( ) ;
switch ( value . size ( ) > 1 & & value . at ( 0 ) = = value . at ( value . size ( ) - 1 )
? value . at ( 0 ) . toLatin1 ( ) : ' \0 ' ) {
case ' " ' : case ' \' ' : {
return value . mid ( 1 , value . size ( ) - 2 )
. split ( QRegularExpression ( QString ( value [ 0 ] ) + " , * "
+ QString ( value [ 0 ] ) ) ) ;
} break ;
default : {
return value . split ( QRegularExpression ( " , * " ) ) ;
}
}
}
static QWebElement find ( QWebFrame * frame , QString selector ,
int repeat = 2 , int sleepsec = 1 ) {
QWebElement element ;
element = find1 ( frame , selector , repeat , sleepsec ) ;
if ( ! element . isNull ( ) ) return element ;
element = find1 ( frame - > page ( ) - > currentFrame ( ) , selector ,
repeat , sleepsec ) ;
if ( ! element . isNull ( ) ) return element ;
element = find1 ( frame - > page ( ) - > mainFrame ( ) , selector ,
repeat , sleepsec ) ;
if ( ! element . isNull ( ) ) return element ;
return element ;
}
static QWebElement find1 ( QWebFrame * frame , QString selector ,
int repeat = 5 , int sleepsec = 1 ) {
QWebElement element ;
for ( int i = 0 ; i < repeat ; + + i ) {
element = frame - > findFirstElement ( selector ) ;
if ( ! element . isNull ( ) ) return element ;
Q_FOREACH ( QWebFrame * childFrame , frame - > childFrames ( ) ) {
element = find1 ( childFrame , selector , 1 , 0 ) ;
if ( ! element . isNull ( ) ) return element ;
}
if ( sleepsec ) sleep ( sleepsec ) ;
}
return element ;
}
void log ( Script * ) ;
bool _log ;
protected :
QString _result ;
private :
int _line ;
QString _file ;
int _indent ;
} ;
class Empty : public Command {
public :
QString tag ( ) const {
return " " ;
}
QString description ( ) const {
return
" "
" \n \n "
" Empty lines are allowed " ;
}
QString command ( ) const {
return tag ( ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Empty > cmd ( new Empty ( ) ) ;
return cmd ;
}
bool execute ( Script * , QWebFrame * ) {
return true ;
}
virtual bool isTestcase ( ) {
return false ;
}
} ;
class Comment : public Command {
public :
Comment ( QString comment ) : _comment ( comment ) { }
QString tag ( ) const {
return " # " ;
}
QString description ( ) const {
return
" # comment "
" \n \n "
" Comments are lines that start with # " ;
}
QString command ( ) const {
return _comment ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Comment > cmd ( new Comment ( args ) ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
this - > log ( false ) ;
Logger log ( this , script ) ;
this - > log ( true ) ;
log ( _comment ) ;
this - > log ( false ) ;
return true ;
}
virtual bool isTestcase ( ) {
return false ;
}
private :
QString _comment ;
} ;
class Screenshot : public Command {
public :
static QString sourceHtml ( Logger & log , int line , QString target , QString base ,
QString name , QWebFrame * frame ) {
if ( ! QDir ( target ) . exists ( ) & & ! QDir ( ) . mkpath ( target ) )
error ( log , DirectoryCannotBeCreated ( target ) ) ;
QCoreApplication : : processEvents ( ) ;
QString filename ( target + QDir : : separator ( ) +
QString ( " %4-%1-%2-%3.html " )
. arg ( base )
. arg ( line , 4 , 10 , QChar ( ' 0 ' ) )
. arg ( name )
. arg ( QDateTime : : currentDateTime ( )
. toString ( " yyyyMMddHHmmss " ) ) ) ;
QFile file ( filename ) ;
if ( ! file . open ( QIODevice : : WriteOnly | QIODevice : : Text ) )
error ( log , CannotWriteSouceHTML ( filename ) ) ;
QTextStream out ( & file ) ;
out < < frame - > toHtml ( ) ;
if ( out . status ( ) ! = QTextStream : : Ok ) error ( log , CannotWriteSouceHTML ( filename ) ) ;
return QDir ( filename ) . absolutePath ( ) ;
}
static QString screenshot ( Logger & log , int line , QString target , QString base ,
QString name , QWebFrame * frame ) {
bool wasShown ( frame - > page ( ) - > view ( ) - > isVisible ( ) ) ;
frame - > page ( ) - > view ( ) - > show ( ) ;
QCoreApplication : : processEvents ( ) ;
QImage image ( frame - > page ( ) - > view ( ) - > size ( ) , QImage : : Format_RGB32 ) ;
QPainter painter ( & image ) ;
frame - > render ( & painter ) ;
painter . end ( ) ;
if ( ! wasShown ) frame - > page ( ) - > view ( ) - > hide ( ) ;
if ( ! QDir ( target ) . exists ( ) & & ! QDir ( ) . mkpath ( target ) )
error ( log , DirectoryCannotBeCreated ( target ) ) ;
QCoreApplication : : processEvents ( ) ;
QString filename ( target + QDir : : separator ( ) +
QString ( " %4-%1-%2-%3.png " )
. arg ( base )
. arg ( line , 4 , 10 , QChar ( ' 0 ' ) )
. arg ( name )
. arg ( QDateTime : : currentDateTime ( )
. toString ( " yyyyMMddHHmmss " ) ) ) ;
if ( ! image . save ( filename ) ) error ( log , CannotWriteScreenshot ( filename ) ) ;
return QDir ( filename ) . absolutePath ( ) ;
}
QString tag ( ) const {
return " screenshot " ;
}
QString description ( ) const {
return
tag ( ) + " <filename-base> "
" \n \n "
" Create a PNG screenshot of the actual web page and store it in the "
" file <filename-base>.png. If not already opened, a browser window "
" will pop up to take the screenshot. " ;
}
QString command ( ) const {
return tag ( ) + " " + _filename ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Screenshot > cmd ( new Screenshot ( ) ) ;
cmd - > _filename = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) ;
private :
QString _filename ;
} ;
class RunDownload : public QObject {
Q_OBJECT ;
public :
RunDownload ( QNetworkReply * reply , QString filename ) :
_reply ( reply ) , _file ( filename ) {
connect ( _reply , SIGNAL ( finished ( ) ) , SLOT ( finished ( ) ) ) ;
connect ( _reply , SIGNAL ( downloadProgress ( qint64 , qint64 ) ) ,
SLOT ( downloadProgress ( qint64 , qint64 ) ) ) ;
_file . open ( QIODevice : : WriteOnly ) ;
}
~ RunDownload ( ) {
disconnect ( _reply , SIGNAL ( finished ( ) ) , this , SLOT ( finished ( ) ) ) ;
disconnect ( _reply , SIGNAL ( downloadProgress ( qint64 , qint64 ) ) ,
this , SLOT ( downloadProgress ( qint64 , qint64 ) ) ) ;
delete _reply ;
}
Q_SIGNALS :
void completed ( bool netsuccess , bool filesuccess ) ;
private Q_SLOTS :
void finished ( ) {
_file . write ( _reply - > readAll ( ) ) ;
_file . close ( ) ;
completed ( _reply - > error ( ) = = QNetworkReply : : NoError ,
_file . error ( ) = = QFileDevice : : NoError ) ;
delete this ;
}
void downloadProgress ( qint64 bytes , qint64 ) {
_file . write ( _reply - > read ( bytes ) ) ;
}
private :
QNetworkReply * _reply ;
QFile _file ;
} ;
/** @mainpage
The WebTester web application testing framework consists of three parts :
- # a gui to create testcases , see @ ref webtester
- # a command line tool to run testcases , see @ ref webrunner
- # test scripts , see @ ref testscript . */
class Script : public QObject {
Q_OBJECT ;
Q_SIGNALS :
void logging ( QString ) ;
void progress ( QString , int , int ) ;
public :
typedef std : : pair < QString , QStringList > Signal ;
enum ClickType {
REAL_MOUSE_CLICK ,
JAVASCRIPT_CLICK
} ;
enum Formatting {
PLAIN ,
HTML
} ;
/// QString that sorts first by string length, then by content
class LenString : public QString {
public :
LenString ( ) { }
LenString ( const QString & o ) : QString ( o ) { }
virtual bool operator < ( const LenString & o ) const {
return
size ( ) < o . size ( ) ? true : o . size ( ) < size ( ) ? false
: QString : : operator < ( o . toLatin1 ( ) ) ;
}
} ;
public :
static QString xmlattr ( QString attr , bool br = false ) {
if ( br ) return attr . toHtmlEscaped ( ) . replace ( " \n " , " <br/> " ) ;
return attr . toHtmlEscaped ( ) ;
}
static QString xmlstr ( const std : : string & attr ) {
return xmlstr ( QString : : fromStdString ( attr ) ) ;
}
static QString xmlstr ( QString attr ) {
return attr
. replace ( " > " , " > " ) . replace ( " < " , " < " )
. replace ( " <br/> " , " \n " ) . replace ( " " " , " \" " )
. replace ( " " , " " ) . replace ( " & " , " & " ) ;
}
public :
Script ( ) : _clicktype ( JAVASCRIPT_CLICK ) , _command ( 0 ) , _screenshots ( true ) {
initPrototypes ( ) ;
}
Script ( const Script & o ) :
QObject ( ) ,
_prototypes ( o . _prototypes ) ,
_script ( o . _script ) ,
_command ( 0 ) ,
_screenshots ( true ) {
set ( o ) ;
}
QString syntax ( ) const {
return
" Script syntax is a text file that consists of list of commands. Each "
" command starts at the begin of a new line. Empty lines are allowed. "
" Lines that start with \" # \" are treated as comments. "
" \n \n "
" Subcommands are indented. The first indented line defines the level of "
" indentation. All following lines must be indented by the same level. "
" \n \n "
" Note: When a selector is required as parameter, then the selector "
" is a CSS selector. "
" \n \n "
" Thanks to the filter script doxygen-webtester.sed, you cab use the "
" comments for producing doxygen documenation. Just start comments with "
" \" ## \" to import them to doxygen. This script is automatically configured, "
" when you use the autotools bootstrap from: \n "
" https://dev.marc.waeckerlin.org/redmine/projects/bootstrap-build-environment " ;
}
/// set workdir
void path ( QString path ) {
_path = ( path . size ( ) ? path : " . " ) + QDir : : separator ( ) ;
}
/// get workdir
QString path ( ) {
return _path ;
}
QString commands ( Formatting f = PLAIN ) const {
QString cmds ;
for ( auto it ( _prototypes . begin ( ) ) ; it ! = _prototypes . end ( ) ; + + it )
switch ( f ) {
case PLAIN : {
cmds + = " \n \n \n COMMAND: " + it - > first + " \n \n " + it - > second - > description ( ) ;
} break ;
case HTML : {
cmds + = " <h1> " + it - > first + " </h1><p> "
+ it - > second - > description ( )
. replace ( " & " , " & " )
. replace ( " < " , " < " )
. replace ( " > " , " > " )
. replace ( QRegularExpression ( " <([^ ]+)> " ) ,
" <i> \\ 1</i> " )
. replace ( QRegularExpression ( " ( \n [^ ][^ \n ]*)( \n +-) " ) ,
" \\ 1<ul> \\ 2 " )
. replace ( QRegularExpression ( " ( \n +-[^ \n ]* \n )([^ ]) " ) ,
" \\ 1</ul> \\ 2 " )
. replace ( QRegularExpression ( " \n +- ([^ \n ]*)(</ul>)? " ) ,
" <li> \\ 1</li> \\ 2 " )
. replace ( " </li> \n " , " </li> " )
. replace ( " \n " , " \n " )
. replace ( " \n \n " , " </p><p> " )
. replace ( " \n " , " <br/> " )
+ " </p> " ;
} break ;
}
return cmds . trimmed ( ) ;
}
void reset ( ) {
_script . clear ( ) ;
while ( ! _signals . empty ( ) ) _signals . pop ( ) ;
_timer . stop ( ) ;
_ignores . clear ( ) ;
_cout . clear ( ) ;
_cerr . clear ( ) ;
_ignoreSignalsUntil . clear ( ) ;
}
void cleanup ( ) {
reset ( ) ;
_variables . clear ( ) ;
_rvariables . clear ( ) ;
_functions . clear ( ) ;
_timeout = 20 ;
_clicktype = JAVASCRIPT_CLICK ;
}
std : : shared_ptr < Command > parseLine ( QStringList & in ,
QString filename , int linenr ,
int indent ) try {
std : : shared_ptr < Command > command ;
if ( ! in . size ( ) ) throw MissingLine ( ) ;
QString line ( in . takeFirst ( ) . trimmed ( ) ) ;
QString cmd ( line ) , args ;
int space ( line . indexOf ( ' ' ) ) ;
if ( space > 0 ) {
cmd = line . left ( space ) ;
args = line . right ( line . size ( ) - space - 1 ) ;
}
Prototypes : : const_iterator it ( _prototypes . find ( cmd ) ) ;
if ( it ! = _prototypes . end ( ) ) {
command = it - > second - > parse ( this , args , in , filename , linenr , indent ) ;
} else {
command = unknown ( line ) ;
}
command - > file ( filename ) ;
command - > line ( linenr ) ;
command - > indent ( indent ) ;
return command ;
} catch ( Exception & e ) {
e . line ( linenr ) ;
e . file ( filename ) ;
throw ;
}
void parse ( QStringList in , QString filename , int line = 1 , int indent = 0 ) {
for ( int linenr ( 0 ) , oldsize ( 0 ) ;
oldsize = in . size ( ) , in . size ( ) ;
linenr + = oldsize - in . size ( ) )
_script . push_back ( parseLine ( in , filename , line + linenr , indent ) ) ;
}
QStringList print ( ) {
QStringList result ;
for ( auto cmd ( _script . begin ( ) ) ; cmd ! = _script . end ( ) ; + + cmd ) {
result + = ( * cmd ) - > command ( ) ;
}
return result ;
}
bool run ( QWebFrame * frame ) {
return run ( frame , _testsuites , targetdir ( ) , _screenshots , _maxretries ) ;
}
bool run ( QWebFrame * frame , std : : shared_ptr < xml : : Node > testsuites ,
QString td = QString ( ) , bool screenshots = true ,
int maxretries = 0 ) {
bool res ( true ) ;
_testsuites = testsuites ;
_timeout = 20 ; // defaults to 20s
_ignoreSignalsUntil . clear ( ) ;
addSignals ( frame ) ;
_screenshots = screenshots ;
_maxretries = maxretries ;
_timer . setSingleShot ( true ) ;
targetdir ( ! td . isEmpty ( )
? td
: _testsuites - > children ( )
? xmlstr ( _testsuites - > last ( ) . attr ( " name " ) )
: " attachments " ) ;
if ( ! _testsuites - > children ( ) ) {
xml : : Node testsuite ( " testsuite " ) ;
testsuite . attr ( " name " ) = " Unnamed Test Suite " ;
( * _testsuites ) < < testsuite ;
}
int retries ( 0 ) , back ( 0 ) , step ( 0 ) ;
for ( auto cmd ( _script . begin ( ) ) ; cmd ! = _script . end ( ) ; + + cmd , + + step ) {
progress ( QString ( " %1:%2 " ) . arg ( ( * cmd ) - > file ( ) ) . arg ( ( * cmd ) - > line ( ) ) ,
step , steps ( ) ) ;
xml : : Node testcase ( " testcase " ) ;
try {
testcase . attr ( " classname " ) =
xmlattr ( _testclass . size ( )
? _testclass
: " file. " + ( * cmd ) - > file ( )
. replace ( QRegularExpression ( " .wt$ " ) , " " )
. replace ( " . " , " - " ) , true )
. toStdString ( ) ;
testcase . attr ( " name " ) =
xmlattr ( QString ( " %1: %2 " )
. arg ( ( * cmd ) - > line ( ) )
. arg ( ( * cmd ) - > command ( ) . split ( ' \n ' ) . takeFirst ( ) , true ) )
. toStdString ( ) ;
if ( ! _ignores . size ( ) | | ( * cmd ) - > tag ( ) = = " label " ) { // not ignored
_timer . start ( _timeout * 1000 ) ;
try {
if ( ! ( res = ( * cmd ) - > execute ( this , frame ) ) ) {
_timer . stop ( ) ;
if ( ! back ) retries = 0 ; else - - back ;
testcase < < ( xml : : String ( " system-out " ) =
xmlattr ( _cout ) . toStdString ( ) ) ;
testcase < < ( xml : : String ( " system-err " ) =
xmlattr ( _cerr ) . toStdString ( ) ) ;
_cout . clear ( ) ;
_cerr . clear ( ) ;
_testsuites - > last ( ) < < testcase ;
break ; // test is successfully finished
}
progress ( QString ( " %1:%2 " ) . arg ( ( * cmd ) - > file ( ) ) . arg ( ( * cmd ) - > line ( ) ) ,
step , steps ( ) ) ;
} catch ( PossibleRetryLoad & e ) {
_timer . stop ( ) ;
// timeout may happen during load due to bad internet connection
if ( screenshots )
try { // take a screenshot on error
Logger log ( 0 , this ) ;
QString filename ( Screenshot : : screenshot
( log , ( * cmd ) - > line ( ) , targetdir ( ) ,
_testclass ,
QString ( " retry-%1 " )
. arg ( ( ulong ) retries , 2 , 10 ,
QLatin1Char ( ' 0 ' ) ) ,
frame ) ) ;
plainlog ( " [[ATTACHMENT| " + filename + " ]] " ) ;
} catch ( . . . ) { } // ignore exception in screenshot
if ( + + retries < = maxretries ) { // retry in that case
QUrl url ( frame - > url ( ) ) ;
if ( ( * cmd ) - > command ( ) = = " expect loadFinished true " ) {
- - - - - - cmd ;
- - - - - - step ;
back + = 3 ;
_ignoreSignalsUntil = " loadStarted " ;
frame - > load ( url ) ;
} else if ( ( * cmd ) - > command ( ) = = " expect loadStarted " ) {
- - - - cmd ;
- - - - step ;
back + = 2 ;
_ignoreSignalsUntil = " loadStarted " ;
frame - > page ( ) - > triggerAction ( QWebPage : : Stop ) ;
} else if ( ( * cmd ) - > command ( ) . startsWith ( " expect urlChanged " ) ) {
QString url2 ( ( * cmd ) - > command ( ) ) ;
url2 . remove ( " expect urlChanged " ) ;
if ( url2 . size ( ) ) url = url2 . trimmed ( ) ;
- - - - cmd ;
- - - - step ;
back + = 2 ;
_ignoreSignalsUntil = " loadStarted " ;
frame - > load ( url ) ;
} else if ( ( * cmd ) - > command ( ) . startsWith ( " expect load " ) ) {
QString url2 ( ( * cmd ) - > command ( ) ) ;
url2 . remove ( " expect load " ) ;
if ( url2 . size ( ) ) url = url2 . trimmed ( ) ;
- - - - cmd ;
- - - - step ;
back + = 2 ;
_ignoreSignalsUntil = " loadStarted " ;
frame - > load ( url ) ;
} else {
throw ;
}
} else {
throw ;
}
log ( QString ( " WARNING: retry#%1, redo last %2 steps; error: %3 " )
. arg ( retries ) . arg ( back ) . arg ( e . what ( ) ) ) ;
}
_timer . stop ( ) ;
if ( ! back ) retries = 0 ; else - - back ;
testcase < < ( xml : : String ( " system-out " ) =
xmlattr ( _cout ) . toStdString ( ) ) ;
testcase < < ( xml : : String ( " system-err " ) =
xmlattr ( _cerr ) . toStdString ( ) ) ;
_cout . clear ( ) ;
_cerr . clear ( ) ;
if ( ( * cmd ) - > isTestcase ( ) )
_testsuites - > last ( ) < < testcase ;
}
} catch ( Exception & e ) {
_timer . stop ( ) ;
if ( ! back ) retries = 0 ; else - - back ;
testcase < < ( xml : : String ( " system-out " ) =
xmlattr ( _cout ) . toStdString ( ) ) ;
testcase < < ( xml : : String ( " system-err " ) =
xmlattr ( _cerr ) . toStdString ( ) ) ;
_cout . clear ( ) ;
_cerr . clear ( ) ;
if ( ( * cmd ) - > isTestcase ( ) )
_testsuites - > last ( ) < < testcase ;
removeSignals ( frame ) ;
e . line ( ( * cmd ) - > line ( ) ) ;
e . file ( ( * cmd ) - > file ( ) ) ;
if ( screenshots )
try { // write html source and take a last screenshot on error
{
Logger log ( 0 , this ) ;
QString filename ( Screenshot : : sourceHtml
( log , ( * cmd ) - > line ( ) , targetdir ( ) ,
_testclass ,
" error " , frame ) ) ;
plainlog ( " [[ATTACHMENT| " + filename + " ]] " ) ;
} {
Logger log ( 0 , this ) ;
QString filename ( Screenshot : : screenshot
( log , ( * cmd ) - > line ( ) , targetdir ( ) ,
_testclass ,
" error " , frame ) ) ;
plainlog ( " [[ATTACHMENT| " + filename + " ]] " ) ;
}
} catch ( . . . ) { } // ignore exception in screenshot
throw ;
}
}
removeSignals ( frame ) ;
if ( ! _signals . empty ( ) ) error ( UnhandledSignals ( _signals ) ) ;
progress ( " success " , 0 , 0 ) ;
return res ;
}
Command * command ( ) {
return _command ;
}
void command ( Command * cmd ) {
_command = cmd ;
}
QString & cout ( ) {
return _cout ;
}
QString & cerr ( ) {
return _cerr ;
}
int steps ( ) {
return _script . size ( ) ;
}
bool screenshots ( ) {
return _screenshots ;
}
void targetdir ( QString name ) {
_targetdir = name ;
}
QString targetdir ( ) {
return _targetdir ;
}
void testclass ( QString tc ) {
_testclass = tc ;
}
QString testclass ( ) {
return _testclass ;
}
void testsuite ( QString name ) {
xml : : Node testsuite ( " testsuite " ) ;
testsuite . attr ( " name " ) = xmlattr ( name , true ) . toStdString ( ) ;
testsuite . attr ( " timestamp " ) =
QDateTime : : currentDateTime ( ) . toString ( Qt : : ISODate ) . toStdString ( ) ;
_testsuites - > last ( ) . attr ( " failures " ) = " 0 " ;
( * _testsuites ) < < testsuite ;
}
Signal getSignal ( ) {
while ( ! _signals . size ( ) ) QCoreApplication : : processEvents ( ) ;
Signal res ( _signals . front ( ) ) ;
_signals . pop ( ) ;
return res ;
}
QTimer & timer ( ) {
return _timer ;
}
void ignoreto ( const QString & l ) {
_ignores . insert ( l ) ;
}
void label ( const QString & l ) {
_ignores . remove ( l ) ;
}
void set ( QString name , QString value ) {
_variables [ name ] = value ;
_rvariables [ value ] = name ;
}
QStringList variables ( ) {
return _variables . keys ( ) ;
}
QString variable ( Logger & log , QString name ) {
QMap < QString , QString > : : iterator it ( _variables . find ( name ) ) ;
if ( it = = _variables . end ( ) ) error ( VariableNotFound ( name ) ) ;
return * it ;
}
/// Copy context from other script
void set ( const Script & o ) {
_variables = o . _variables ;
_rvariables = o . _rvariables ;
_timeout = o . _timeout ;
_clicktype = o . _clicktype ;
_testsuites = o . _testsuites ;
_testclass = o . _testclass ;
_targetdir = o . _targetdir ;
_maxretries = o . _maxretries ;
_screenshots = o . _screenshots ;
_cout . clear ( ) ;
_cerr . clear ( ) ;
_ignoreSignalsUntil . clear ( ) ;
_functions . unite ( o . _functions ) ;
}
void unset ( QString name ) {
_rvariables . remove ( _variables [ name ] ) ;
_variables . remove ( name ) ;
}
QStringList functions ( ) {
return _functions . keys ( ) ;
}
void function ( QString name , std : : shared_ptr < Function > f ) {
_functions [ name ] = f ;
}
std : : shared_ptr < Function > function ( Logger & log , QString name ) {
QMap < QString , std : : shared_ptr < Function > > : : iterator
it ( _functions . find ( name ) ) ;
if ( it = = _functions . end ( ) ) error ( FunctionNotFound ( name ) ) ;
return * it ;
}
void timeout ( int t ) {
_timeout = t ;
}
void clicktype ( ClickType c ) {
_clicktype = c ;
}
ClickType clicktype ( ) {
return _clicktype ;
}
QString replacevars ( QString txt ) {
for ( QMap < QString , QString > : : iterator it ( _variables . begin ( ) ) ;
it ! = _variables . end ( ) ; + + it )
txt . replace ( it . key ( ) , it . value ( ) , Qt : : CaseSensitive ) ;
return txt ;
}
QStringList replacevars ( QStringList txts ) {
for ( QStringList : : iterator txt ( txts . begin ( ) ) ; txt ! = txts . end ( ) ; + + txt )
* txt = replacevars ( * txt ) ;
return txts ;
}
QString insertvars ( QString txt ) {
QMapIterator < LenString , LenString > it ( _rvariables ) ;
it . toBack ( ) ;
while ( it . hasPrevious ( ) ) {
it . previous ( ) ;
txt . replace ( it . key ( ) , it . value ( ) , Qt : : CaseSensitive ) ;
}
return txt ;
}
QString result ( ) {
if ( _script . size ( ) ) return ( * _script . rbegin ( ) ) - > result ( ) ;
return QString ( ) ;
}
void addSignals ( QWebFrame * frame ) {
connect ( dynamic_cast < NetworkAccessManager * >
( frame - > page ( ) - > networkAccessManager ( ) ) ,
SIGNAL ( log ( QString ) ) ,
SLOT ( log ( QString ) ) ) ;
connect ( frame , SIGNAL ( contentsSizeChanged ( const QSize & ) ) ,
SLOT ( contentsSizeChanged ( const QSize & ) ) ) ;
connect ( frame , SIGNAL ( iconChanged ( ) ) ,
SLOT ( iconChanged ( ) ) ) ;
connect ( frame , SIGNAL ( initialLayoutCompleted ( ) ) ,
SLOT ( initialLayoutCompleted ( ) ) ) ;
connect ( frame , SIGNAL ( javaScriptWindowObjectCleared ( ) ) ,
SLOT ( javaScriptWindowObjectCleared ( ) ) ) ;
connect ( frame , SIGNAL ( loadFinished ( bool ) ) ,
SLOT ( loadFinished ( bool ) ) ) ;
connect ( frame , SIGNAL ( loadStarted ( ) ) ,
SLOT ( loadStarted ( ) ) ) ;
connect ( frame , SIGNAL ( titleChanged ( const QString & ) ) ,
SLOT ( titleChanged ( const QString & ) ) ) ;
connect ( frame , SIGNAL ( urlChanged ( const QUrl & ) ) ,
SLOT ( urlChanged ( const QUrl & ) ) ) ;
connect ( & _timer , SIGNAL ( timeout ( ) ) , SLOT ( timeout ( ) ) ) ;
}
void removeSignals ( QWebFrame * frame ) {
disconnect ( dynamic_cast < NetworkAccessManager * >
( frame - > page ( ) - > networkAccessManager ( ) ) ,
SIGNAL ( log ( QString ) ) ,
this , SLOT ( log ( QString ) ) ) ;
disconnect ( frame , SIGNAL ( contentsSizeChanged ( const QSize & ) ) ,
this , SLOT ( contentsSizeChanged ( const QSize & ) ) ) ;
disconnect ( frame , SIGNAL ( iconChanged ( ) ) ,
this , SLOT ( iconChanged ( ) ) ) ;
disconnect ( frame , SIGNAL ( initialLayoutCompleted ( ) ) ,
this , SLOT ( initialLayoutCompleted ( ) ) ) ;
disconnect ( frame , SIGNAL ( javaScriptWindowObjectCleared ( ) ) ,
this , SLOT ( javaScriptWindowObjectCleared ( ) ) ) ;
disconnect ( frame , SIGNAL ( loadFinished ( bool ) ) ,
this , SLOT ( loadFinished ( bool ) ) ) ;
disconnect ( frame , SIGNAL ( loadStarted ( ) ) ,
this , SLOT ( loadStarted ( ) ) ) ;
disconnect ( frame , SIGNAL ( titleChanged ( const QString & ) ) ,
this , SLOT ( titleChanged ( const QString & ) ) ) ;
disconnect ( frame , SIGNAL ( urlChanged ( const QUrl & ) ) ,
this , SLOT ( urlChanged ( const QUrl & ) ) ) ;
disconnect ( frame , SIGNAL ( urlChanged ( const QUrl & ) ) ,
this , SLOT ( urlChanged ( const QUrl & ) ) ) ;
disconnect ( & _timer , SIGNAL ( timeout ( ) ) , this , SLOT ( timeout ( ) ) ) ;
}
public Q_SLOTS :
void log ( QString text , Command * command = 0 ) {
QString prefix
( QDateTime : : currentDateTime ( ) . toString ( " yyyy-MM-dd HH:mm:ss " ) ) ;
Command * cmd ( command ? command : _command ) ;
for ( QChar & c : text ) if ( c < 32 & & c ! = ' \n ' ) c = ' ? ' ;
if ( cmd )
prefix + = QString ( " %2:%3%1 " )
. arg ( QString ( cmd - > indent ( ) , QChar ( ' ' ) ) )
. arg ( cmd - > file ( ) , 20 , QChar ( ' ' ) )
. arg ( cmd - > line ( ) , - 4 , 10 , QChar ( ' ' ) ) ;
text = prefix + text . split ( ' \n ' ) . join ( " \n " + prefix + " " ) ;
logging ( text ) ;
std : : cout < < text < < std : : endl < < std : : flush ;
_cout + = text + " \n " ;
}
void plainlog ( QString text ) {
logging ( text ) ;
std : : cout < < text < < std : : endl < < std : : flush ;
_cout + = text + " \n " ;
}
void parentlog ( QString text ) {
logging ( text ) ;
_cout + = text + " \n " ;
}
private :
void error ( const Exception & e ) {
log ( QString ( " FAILED: " ) + e . what ( ) ) ;
throw e ;
}
std : : shared_ptr < Command > unknown ( QString command ) {
if ( ! command . size ( ) )
return std : : shared_ptr < Command > ( new Empty ( ) ) ;
if ( command [ 0 ] = = ' # ' )
return std : : shared_ptr < Command > ( new Comment ( command ) ) ;
throw UnknownCommand ( command ) ; // error
}
void initPrototypes ( ) ;
void add ( Command * c ) {
_prototypes [ c - > tag ( ) ] = std : : shared_ptr < Command > ( c ) ;
}
private Q_SLOTS :
void contentsSizeChanged ( const QSize & ) {
}
void iconChanged ( ) {
}
void initialLayoutCompleted ( ) {
}
void javaScriptWindowObjectCleared ( ) {
}
void loadFinished ( bool ok ) {
QString sig ( ok ? " true " : " false " ) ;
if ( _ignoreSignalsUntil . size ( ) & &
_ignoreSignalsUntil ! = " loadFinished " + sig ) {
log ( " warning: ignored loadFinished, waiting for " + _ignoreSignalsUntil ) ;
return ;
}
_ignoreSignalsUntil . clear ( ) ;
log ( " signal received: loadFinished " + QString ( ok ? " true " : " false " ) ) ;
_signals . push ( std : : make_pair ( " loadFinished " , QStringList ( sig ) ) ) ;
}
void loadStarted ( ) {
if ( _ignoreSignalsUntil . size ( ) & & _ignoreSignalsUntil ! = " loadStarted " ) {
log ( " warning: ignored loadStarted, waiting for " + _ignoreSignalsUntil ) ;
return ;
}
_ignoreSignalsUntil . clear ( ) ;
log ( " signal received: loadStarted " ) ;
_signals . push ( std : : make_pair ( " loadStarted " , QStringList ( ) ) ) ;
}
void frameChanged ( ) {
}
void titleChanged ( const QString & ) {
//_signals.push(std::make_pair("titleChanged", QStringList(title)));
}
void urlChanged ( const QUrl & url ) {
if ( _ignoreSignalsUntil . size ( ) & & _ignoreSignalsUntil ! = " urlChanged " ) {
log ( " warning: ignored urlChanged, waiting for " + _ignoreSignalsUntil ) ;
return ;
}
_ignoreSignalsUntil . clear ( ) ;
log ( " signal received: urlChanged " + url . toString ( ) ) ;
_signals . push ( std : : make_pair ( " urlChanged " ,
QStringList ( url . toString ( ) ) ) ) ;
}
void timeout ( ) {
error ( TimeOut ( ) ) ;
}
private :
typedef std : : map < QString , std : : shared_ptr < Command > > Prototypes ;
typedef std : : vector < std : : shared_ptr < Command > > Commands ;
Prototypes _prototypes ;
Commands _script ;
std : : queue < Signal > _signals ;
QTimer _timer ;
QSet < QString > _ignores ;
QString _cout ;
QString _cerr ;
bool _screenshots ;
int _maxretries ;
QString _ignoreSignalsUntil ;
QMap < QString , QString > _variables ; ///< variable mapping
QMap < LenString , LenString > _rvariables ; ///< reverse variable mapping
QMap < QString , std : : shared_ptr < Function > > _functions ;
int _timeout ;
ClickType _clicktype ;
QString _targetdir ;
std : : shared_ptr < xml : : Node > _testsuites ; ///< only valid within run
QString _testclass ;
Command * _command ;
QString _path ;
} ;
class Do : public Command {
public :
QString tag ( ) const {
return " do " ;
}
QString description ( ) const {
return
tag ( ) + " [<selector>] \n <javascript-line1> \n <javascript-line2> "
" \n \n "
" Execute JavaScript on a CSS selected object. The object is the first "
" object in the DOM tree that matches the given CSS selector. You can "
" refere to the selected object within the scripy by \" this \" . The "
" JavaScript code is on the following lines and at least intended by "
" one space " ;
}
QString command ( ) const {
return tag ( ) + " " + _selector + ( _javascript . size ( ) ? " \n " + _javascript : " " ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & in , QString , int , int ) {
std : : shared_ptr < Do > cmd ( new Do ( ) ) ;
cmd - > _selector = args . trimmed ( ) ;
cmd - > _javascript = subCommandBlock ( in ) . join ( " \n " ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QWebElement element ( frame - > documentElement ( ) ) ;
if ( _selector . size ( ) ) {
element = find ( frame , script - > replacevars ( _selector ) ) ;
if ( element . isNull ( ) )
error ( log , ElementNotFound ( script - > replacevars ( _selector ) ) ) ;
}
_result =
element . evaluateJavaScript ( script - > replacevars ( _javascript ) ) . toString ( ) ;
log ( " result: " + ( _result . size ( ) ? _result : " (void) " ) ) ;
return true ;
}
private :
QString _selector ;
QString _javascript ;
} ;
class Load : public Command {
public :
QString tag ( ) const {
return " load " ;
}
QString description ( ) const {
return
tag ( ) + " <url> "
" \n \n "
" Load an URL, the URL is given as parameter in full syntax. " ;
}
QString command ( ) const {
return tag ( ) + " " + _url ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Load > cmd ( new Load ( ) ) ;
cmd - > _url = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
frame - > load ( script - > replacevars ( _url ) ) ;
return true ;
}
private :
QString _url ;
} ;
class Expect : public Command {
public :
QString tag ( ) const {
return " expect " ;
}
QString description ( ) const {
return
tag ( ) + " <signal> [<parameter>] "
" \n \n "
" Expect a signal. Signals are emitted by webkit and may contain "
" parameter. If a parameter is given in the script, then the parameter "
" must match exactly. If no parameter is given, then the signal must "
" be emitted, but the parameters of the signal are not checked. "
" \n \n "
" Known signals and parameters are: \n "
" - loadFinished <bool: \" true \" if ok, \" false \" on error> \n "
" - loadStarted \n "
" - urlChanged <url> \n "
" There is a short hand: \n "
" - load <url> \n "
" stands for the three signals: \n "
" - loadStarted (optional) \n "
" - urlChanged <url> \n "
" - loadFinished true " ;
}
QString command ( ) const {
return tag ( ) + " " + _signal . _signal
+ ( _signal . _args . size ( ) ? " " + _signal . _args . join ( ' ' ) : QString ( ) ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Expect > cmd ( new Expect ( ) ) ;
cmd - > _signal . _args = args . split ( " " ) ;
cmd - > _signal . _signal = cmd - > _signal . _args . takeFirst ( ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
QStringList args ( script - > replacevars ( _signal . _args ) ) ;
Script : : Signal lastsignal ( script - > getSignal ( ) ) ;
if ( _signal . _signal = = " load " ) { // special signal load
while ( lastsignal . first = = " loadStarted " ) {
log ( " ignore signal: loadStarted " ) ;
lastsignal = script - > getSignal ( ) ; // ignore optional loadStarted
}
if ( lastsignal . first ! = " urlChanged " | | ( args . size ( ) & & lastsignal . second ! = args ) )
error ( log , WrongSignal ( " urlChanged " , args , lastsignal ) ) ;
lastsignal = script - > getSignal ( ) ;
args = QStringList ( " true " ) ;
if ( lastsignal . first ! = " loadFinished " | | ( lastsignal . second ! = args ) )
error ( log , WrongSignal ( " loadFinished " , args , lastsignal ) ) ;
} else {
if ( lastsignal . first ! = _signal . _signal | | ( args . size ( ) & & lastsignal . second ! = args ) )
error ( log , WrongSignal ( _signal . _signal , args , lastsignal ) ) ;
}
return true ;
}
private :
struct Signal {
Signal ( QString s , QStringList a ) : _signal ( s ) , _args ( a ) { }
Signal ( QString s , QString a ) : _signal ( s ) , _args ( a ) { }
Signal ( QString s ) : _signal ( s ) { }
Signal ( ) { }
QString _signal ;
QStringList _args ;
} ;
Signal _signal ;
} ;
class Open : public Command {
public :
QString tag ( ) const {
return " open " ;
}
QString description ( ) const {
return
tag ( ) +
" \n \n "
" Open the browser window, so you can follow the test steps visually. " ;
}
QString command ( ) const {
return tag ( ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Open > cmd ( new Open ( ) ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
frame - > page ( ) - > view ( ) - > show ( ) ;
return true ;
}
} ;
class Sleep : public Command {
public :
QString tag ( ) const {
return " sleep " ;
}
QString description ( ) const {
return
tag ( ) + " <seconds> "
" \n \n "
" Sleep for a certain amount of seconds. This helps, if you must wait "
" for some javascript actions, i.e. AJAX or slow pages, and the "
" excpeted signals are not sufficient. " ;
}
QString command ( ) const {
return tag ( ) + " " + _time ;
}
std : : shared_ptr < Command > parse ( Script * , QString time ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Sleep > cmd ( new Sleep ( ) ) ;
cmd - > _time = " 10 " ; // default: 10s
if ( time . size ( ) ) cmd - > _time = time ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
script - > timer ( ) . stop ( ) ;
bool ok ;
unsigned int time ( script - > replacevars ( _time ) . toUInt ( & ok ) ) ;
if ( ! ok )
error ( log , BadArgument ( script - > replacevars ( _time )
+ " should be a number of seconds " ) ) ;
sleep ( time ) ;
return true ;
}
private :
QString _time ;
} ;
class Exit : public Command {
public :
QString tag ( ) const {
return " exit " ;
}
QString description ( ) const {
return
tag ( ) +
" \n \n "
" Successfully terminate script immediately. The following commands "
" are not executed. This helps when you debug your scripts and you "
" want the script stop at a certain point for investigations. " ;
}
QString command ( ) const {
return tag ( ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Exit > cmd ( new Exit ( ) ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
return false ;
}
} ;
class IgnoreTo : public Command {
public :
QString tag ( ) const {
return " ignoreto " ;
}
QString description ( ) const {
return
tag ( ) + " <label> "
" \n \n "
" Ignore all following commands up to a given label. The following "
" commands are not executed until the given label appears in the "
" script. This helps when you debug your scripts and you "
" want to skip some lines of script code. " ;
}
QString command ( ) const {
return tag ( ) + " " + _label ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < IgnoreTo > cmd ( new IgnoreTo ( ) ) ;
if ( ! args . size ( ) ) throw BadArgument ( " ignoreto needs a label " ) ;
cmd - > _label = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
script - > ignoreto ( script - > replacevars ( _label ) ) ;
return true ;
}
private :
QString _label ;
} ;
class Label : public Command {
public :
QString tag ( ) const {
return " label " ;
}
QString description ( ) const {
return
tag ( ) + " <label> "
" \n \n "
" This marks the label refered by command \" ignoreto \" . " ;
}
QString command ( ) const {
return tag ( ) + " " + _label ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Label > cmd ( new Label ( ) ) ;
if ( ! args . size ( ) ) throw BadArgument ( " label needs a label as parameter " ) ;
cmd - > _label = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
script - > label ( script - > replacevars ( _label ) ) ;
return true ;
}
private :
QString _label ;
} ;
class Upload : public Command {
public :
QString tag ( ) const {
return " upload " ;
}
QString description ( ) const {
return
tag ( ) + " <selector> -> <filename> "
" \n \n "
" Presses the specified file upload button and passes a given file "
" name. The command requires a CSS selector followed by a filename. "
" The first object that matches the selector is used. " ;
}
QString command ( ) const {
return tag ( ) + " " + _selector + " -> " + _filename ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Upload > cmd ( new Upload ( ) ) ;
QStringList allargs ( args . split ( " -> " ) ) ;
if ( allargs . size ( ) < 2 )
throw BadArgument ( tag ( ) + " requires <selector> -> <filename>, "
" instead of: \" " + args + " \" " ) ;
cmd - > _selector = allargs . takeFirst ( ) . trimmed ( ) ;
cmd - > _filename = allargs . join ( " -> " ) . trimmed ( ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
TestWebPage * page ( dynamic_cast < TestWebPage * > ( frame - > page ( ) ) ) ;
assert ( page ) ;
QString filename ( script - > path ( ) + script - > replacevars ( _filename ) ) ;
if ( ! QFileInfo ( filename ) . exists ( ) ) {
QStringList files ( QFileInfo ( filename ) . dir ( )
. entryList ( QStringList ( filename ) ) ) ;
if ( files . size ( ) = = 1 ) filename = files [ 0 ] ;
}
if ( ! QFileInfo ( filename ) . exists ( ) ) filename = script - > replacevars ( _filename ) ;
if ( ! QFileInfo ( filename ) . exists ( ) ) {
QStringList files ( QFileInfo ( filename ) . dir ( )
. entryList ( QStringList ( filename ) ) ) ;
if ( files . size ( ) = = 1 ) filename = files [ 0 ] ;
}
page - > setNextUploadFile ( filename ) ;
realMouseClick ( log , frame , script - > replacevars ( _selector ) ) ;
if ( page - > uploadPrepared ( ) )
error ( log , SetFileUploadFailed ( script - > replacevars ( _selector ) , filename ) ) ;
return true ;
}
private :
QString _selector ;
QString _filename ;
} ;
class Exists : public Command {
public :
QString tag ( ) const {
return " exists " ;
}
QString description ( ) const {
return
tag ( ) + " <selector> -> <text> "
" \n \n "
" Assert that a certain text exists in the selected object, or if no "
" text is given, assert that the specified object exists. The object "
" is given by a CSS selector. All matching objects are search for the "
" text. " ;
}
QString command ( ) const {
return tag ( ) + " " + _selector + ( _text . size ( ) ? " -> " + _text : QString ( ) ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Exists > cmd ( new Exists ( ) ) ;
QStringList allargs ( args . split ( " -> " ) ) ;
if ( allargs . size ( ) < 2 ) {
cmd - > _selector = args ;
} else {
cmd - > _selector = allargs . takeFirst ( ) . trimmed ( ) ;
cmd - > _text = allargs . join ( " -> " ) . trimmed ( ) ;
}
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QString selector ( script - > replacevars ( _selector ) ) ;
QString text ( script - > replacevars ( _text ) ) ;
QStringList notfound ;
Q_FOREACH ( QWebElement element , frame - > findAllElements ( selector ) ) {
if ( text . isEmpty ( ) ) return true ; // just find element
if ( element . toOuterXml ( ) . indexOf ( text ) ! = - 1 ) return true ;
if ( element . toPlainText ( ) . indexOf ( text ) ! = - 1 ) return true ;
notfound + = element . toOuterXml ( ) ;
}
QWebElement element ( find ( frame , selector ) ) ;
if ( text . isEmpty ( ) )
error ( log , AssertionFailed ( " element not found: " + selector ) ) ;
else if ( element . isNull ( ) )
error ( log , AssertionFailed ( " expected \" " + text + " \" in non existing element "
+ selector ) ) ;
else
error ( log , AssertionFailed ( " not found \" " + text + " \" in \" "
+ notfound . join ( " \" , \" " ) + " \" on " + selector ) ) ;
return true ; // never reached due to error, above
}
private :
QString _selector ;
QString _text ;
} ;
class Not : public Command {
public :
QString tag ( ) const {
return " not " ;
}
QString description ( ) const {
return
tag ( ) + " <selector> -> <text> "
" \n \n "
" Assert that a certain text does not exists in the selected object, "
" or if no text is given, assert that the specified object does not "
" exists. The object is given by a CSS selector. All matching objects "
" are search for the text. " ;
}
QString command ( ) const {
return tag ( ) + " " + _selector + ( _text . size ( ) ? " -> " + _text : QString ( ) ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Not > cmd ( new Not ( ) ) ;
QStringList allargs ( args . split ( " -> " ) ) ;
if ( allargs . size ( ) < 2 ) {
cmd - > _selector = args ;
} else {
cmd - > _selector = allargs . takeFirst ( ) . trimmed ( ) ;
cmd - > _text = allargs . join ( " -> " ) . trimmed ( ) ;
}
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QString selector ( script - > replacevars ( _selector ) ) ;
QString text ( script - > replacevars ( _text ) ) ;
Q_FOREACH ( QWebElement element , frame - > findAllElements ( selector ) ) {
if ( text . isEmpty ( ) )
error ( log , AssertionFailed ( " element must not exists: " + selector ) ) ;
if ( element . toOuterXml ( ) . indexOf ( text ) ! = - 1 )
error ( log , AssertionFailed ( " \" " + text + " \" must not be in \" "
+ element . toInnerXml ( ) + " \" on " + selector ) ) ;
}
return true ;
}
private :
QString _selector ;
QString _text ;
} ;
class Execute : public Command {
public :
QString tag ( ) const {
return " execute " ;
}
QString description ( ) const {
return
tag ( ) + " <command> \n <line1> \n <line2> \n <...> "
" \n \n "
" Execute <command>. The command can have space separated arguments. "
" Following lines that are intended by at least "
" one space are streamed into the command. This way, you can e.g. "
" execute a bash script to check a file you downloaded from a page. " ;
}
QString command ( ) const {
QStringList script ( _script ) ;
script . replaceInStrings ( QRegularExpression ( " ^ " ) , " " ) ;
return tag ( ) + " " + _command
+ ( _args . size ( ) ? " " + _args . join ( ' ' ) : QString ( ) )
+ ( script . size ( ) ? " \n " + script . join ( " \n " ) : QString ( ) ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & in , QString , int , int ) {
std : : shared_ptr < Execute > cmd ( new Execute ( ) ) ;
cmd - > _args = args . split ( ' ' ) ;
cmd - > _command = cmd - > _args . takeFirst ( ) ;
cmd - > _script = subCommandBlock ( in ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
script - > timer ( ) . stop ( ) ;
QString command ( script - > replacevars ( _command ) ) ;
QStringList args ;
QString scripttxt ( script - > replacevars ( _script . join ( " \n " ) ) ) ;
Q_FOREACH ( QString arg , _args ) args . push_back ( script - > replacevars ( arg ) ) ;
QProcess exec ;
exec . setProcessChannelMode ( QProcess : : MergedChannels ) ;
exec . start ( command , args ) ;
if ( ! exec . waitForStarted ( ) )
error ( log , CannotStartScript ( command , args , scripttxt ) ) ;
if ( scripttxt . size ( ) ) {
if ( exec . write ( scripttxt . toUtf8 ( ) ) ! = scripttxt . toUtf8 ( ) . size ( ) | |
! exec . waitForBytesWritten ( 60000 ) )
error ( log , CannotLoadScript ( command , args , scripttxt ) ) ;
}
exec . closeWriteChannel ( ) ;
if ( ! exec . waitForFinished ( 60000 ) & & exec . state ( ) ! = QProcess : : NotRunning )
error ( log , ScriptNotFinished ( command , args , scripttxt ) ) ;
QString stdout ( exec . readAllStandardOutput ( ) ) ;
QString stderr ( exec . readAllStandardError ( ) ) ;
_result = stdout ;
log ( " result: " + ( _result . size ( ) ? _result : " (void) " ) ) ;
script - > log ( stdout ) ;
if ( exec . exitCode ( ) ! = 0 | | exec . exitStatus ( ) ! = QProcess : : NormalExit )
error ( log , ScriptExecutionFailed ( command , args , scripttxt ,
exec . exitCode ( ) , stdout , stderr ) ) ;
return true ;
}
private :
QString _command ;
QStringList _args ;
QStringList _script ;
} ;
class Download : public Command {
Q_OBJECT ;
public :
QString tag ( ) const {
return " download " ;
}
QString description ( ) const {
return
tag ( ) + " <filename> \n "
" <command-to-start-download> "
" \n \n "
" Set download file before loading a download link or clicking on a "
" download button. The next line must be exactly one command that "
" initiates the download. \n \n "
" Please note that variables are not substituted in the filename. " ;
}
QString command ( ) const {
return tag ( ) + ( _filename . size ( ) ? " " + _filename : QString ( ) ) + " \n "
+ _next - > command ( ) ;
}
std : : shared_ptr < Command > parse ( Script * script , QString args ,
QStringList & in , QString file , int line ,
int indent ) {
std : : shared_ptr < Download > cmd ( new Download ( ) ) ;
cmd - > _filename = args . trimmed ( ) ;
cmd - > _next = script - > parseLine ( in , file , line + 1 , indent + 1 ) ;
cmd - > _next - > log ( false ) ; // suppress logging of subcommand
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
_realfilename = script - > replacevars ( _filename ) ;
log ( " REALFILENAME= " + _realfilename ) ;
frame - > page ( ) - > setForwardUnsupportedContent ( true ) ;
connect ( frame - > page ( ) , SIGNAL ( unsupportedContent ( QNetworkReply * ) ) ,
this , SLOT ( unsupportedContent ( QNetworkReply * ) ) ) ;
try {
bool res ( _next - > execute ( script , frame ) ) ; // start download
script - > timer ( ) . stop ( ) ; // no timeout during download
for ( _done = false ; ! _done ; ) // wait for download finish
QCoreApplication : : processEvents ( QEventLoop : : AllEvents , 100 ) ;
log ( " download terminated " +
QString ( _netsuccess & & _filesuccess ? " successfully " : " with error " ) ) ;
if ( ! _netsuccess ) error ( log , DownloadFailed ( _realfilename ) ) ;
if ( ! _filesuccess ) error ( log , WriteFileFailed ( _realfilename ) ) ;
log [ " [[ATTACHMENT| " + QDir ( _realfilename ) . absolutePath ( ) + " ]] " ] ;
disconnect ( frame - > page ( ) , SIGNAL ( unsupportedContent ( QNetworkReply * ) ) ,
this , SLOT ( unsupportedContent ( QNetworkReply * ) ) ) ;
return res ;
} catch ( . . . ) {
disconnect ( frame - > page ( ) , SIGNAL ( unsupportedContent ( QNetworkReply * ) ) ,
this , SLOT ( unsupportedContent ( QNetworkReply * ) ) ) ;
throw ;
}
}
private Q_SLOTS :
void completed ( bool netsuccess , bool filesuccess ) {
_done = true ;
_netsuccess = netsuccess ;
_filesuccess = filesuccess ;
}
void unsupportedContent ( QNetworkReply * reply ) {
if ( ! _realfilename . size ( ) ) {
_realfilename = 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 ( ) ) _realfilename = part ;
}
}
}
connect ( new RunDownload ( reply , _realfilename ) ,
SIGNAL ( completed ( bool , bool ) ) , SLOT ( completed ( bool , bool ) ) ) ;
}
private :
QString _filename ;
QString _realfilename ;
std : : shared_ptr < Command > _next ;
bool _done , _netsuccess , _filesuccess ;
} ;
class Click : public Command {
public :
QString tag ( ) const {
return " click " ;
}
QString description ( ) const {
return
tag ( ) + " [<clicktype>] <selector> "
" \n \n "
" Click on the specified element. Either you explicitely specify a click "
" type, such as <realmouse> or <javascript>, or the previously set or "
" the default clicktype is used. " ;
}
QString command ( ) const {
return tag ( ) + ( _clicktype
? ( * _clicktype = = Script : : REAL_MOUSE_CLICK ? " realmouse "
: * _clicktype = = Script : : JAVASCRIPT_CLICK ? " javascript " : " " )
: " " ) + " " + _selector ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Click > cmd ( new Click ( ) ) ;
if ( args . trimmed ( ) . contains ( QRegularExpression ( " ^realmouse " ) ) )
cmd - > _clicktype = Script : : REAL_MOUSE_CLICK ;
else if ( args . trimmed ( ) . contains ( QRegularExpression ( " ^javascript " ) ) )
cmd - > _clicktype = Script : : JAVASCRIPT_CLICK ;
cmd - > _selector =
args . remove ( QRegularExpression ( " ^ *(realmouse|javascript) + " ) ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QString clicktarget ( script - > replacevars ( _selector ) ) ;
switch ( _clicktype ? * _clicktype : script - > clicktype ( ) ) {
case Script : : REAL_MOUSE_CLICK : {
realMouseClick ( log , frame , clicktarget ) ;
break ;
}
case Script : : JAVASCRIPT_CLICK :
default : {
QWebElement element ( find ( frame , clicktarget ) ) ;
if ( element . isNull ( ) )
error ( log , ElementNotFound ( clicktarget ) ) ;
_result = element . evaluateJavaScript ( " this.click(); " ) . toString ( ) ;
if ( _result . size ( ) ) log ( " result: " + _result . size ( ) ) ;
break ;
}
}
return true ;
}
private :
QString _selector ;
std : : optional < Script : : ClickType > _clicktype ;
} ;
class Set : public Command {
public :
QString tag ( ) const {
return " set " ;
}
QString description ( ) const {
return
tag ( ) + " <variable>=<value> \n " +
tag ( ) + " <variable> \n "
" <command> "
" \n \n "
" Sets the value of a variable either to a constant, or to the output "
" of a command. A command should be a command that produces an "
" output, such as <do>, which returns the result of JavaScript or "
" <execute>, which returns the output of the executed command, or "
" <call>, which returns the result of the last command. "
" All variables are global with regard to functions. " ;
}
QString command ( ) const {
if ( _next )
return tag ( ) + " " + _name + " \n " + _next - > command ( ) ;
else
return tag ( ) + " " + _name + " = " + _value ;
}
std : : shared_ptr < Command > parse ( Script * script , QString args ,
QStringList & in , QString file , int line ,
int indent ) {
std : : shared_ptr < Set > cmd ( new Set ( ) ) ;
cmd - > _next = 0 ;
QStringList allargs ( args . split ( " = " ) ) ;
cmd - > _name = allargs . takeFirst ( ) . trimmed ( ) ;
cmd - > _value = allargs . join ( " = " ) . trimmed ( ) ;
if ( ! args . contains ( ' = ' ) ) {
cmd - > _next = script - > parseLine ( in , file , line + 1 , indent + 1 ) ;
cmd - > _next - > log ( false ) ; // suppress logging of subcommand
}
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
if ( _next ) {
_next - > execute ( script , frame ) ;
script - > set ( _name ,
script - > replacevars ( _next - > result ( ) ) ) ;
log ( _name + " =' " + _next - > result ( ) . replace ( " \\ " , " \\ \\ " ) . replace ( " ' " , " \\ ' " ) + " ' " ) ;
} else {
script - > set ( _name ,
script - > replacevars ( _value ) ) ;
log ( _name + " =' " + _value . replace ( " \\ " , " \\ \\ " ) . replace ( " ' " , " \\ ' " ) + " ' " ) ;
}
return true ;
}
private :
QString _name ;
QString _value ;
std : : shared_ptr < Command > _next ;
} ;
class UnSet : public Command {
public :
QString tag ( ) const {
return " unset " ;
}
QString description ( ) const {
return
tag ( ) + " <variable> "
" \n \n "
" Undo the setting of a variable. The opposite of «set». " ;
}
QString command ( ) const {
return tag ( ) + " " + _name ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < UnSet > cmd ( new UnSet ( ) ) ;
cmd - > _name = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
script - > unset ( _name ) ;
return true ;
}
private :
QString _name ;
} ;
class Timeout : public Command {
public :
QString tag ( ) const {
return " timeout " ;
}
QString description ( ) const {
return
tag ( ) + " <seconds> "
" \n \n "
" Set the timeout in seconds (defaults to 10). " ;
}
QString command ( ) const {
return tag ( ) + " " + _timeout ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Timeout > cmd ( new Timeout ( ) ) ;
cmd - > _timeout = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
bool ok ;
int timeout ( script - > replacevars ( _timeout ) . toInt ( & ok ) ) ;
if ( ! ok ) error ( log , BadArgument ( script - > replacevars ( _timeout )
+ " should be a number of seconds " ) ) ;
script - > timeout ( timeout ) ;
return true ;
}
private :
QString _timeout ;
} ;
class CaCertificate : public Command {
public :
QString tag ( ) const {
return " ca-certificate " ;
}
QString description ( ) const {
return
tag ( ) + " <filename.pem> "
" \n \n "
" Load a CA certificate that will be accepted on SSL connections. " ;
}
QString command ( ) const {
return tag ( ) + " " + _filename ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < CaCertificate > cmd ( new ( CaCertificate ) ) ;
cmd - > _filename = args . trimmed ( ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
QString filename ( script - > replacevars ( _filename ) ) ;
QFile cacertfile ( script - > path ( ) + filename ) ;
if ( ! cacertfile . exists ( ) ) cacertfile . setFileName ( filename ) ; // try without path
if ( ! cacertfile . exists ( ) | | ! cacertfile . open ( QIODevice : : ReadOnly ) )
error ( log , FileNotFound ( filename ) ) ;
QSslCertificate cacert ( & cacertfile ) ;
if ( cacert . isNull ( ) ) error ( log , NotACertificate ( filename ) ) ;
QSslSocket : : addDefaultCaCertificate ( cacert ) ;
return true ;
}
private :
QString _filename ;
} ;
class ClientCertificate : public Command {
public :
QString tag ( ) const {
return " client-certificate " ;
}
QString description ( ) const {
return
tag ( ) + " <certfile.pem> <keyfile.pem> <keypassword> "
" \n \n "
" Load a client certificate to authenticate on SSL connections. "
" The password for the keyfile should not contain spaces. "
" Create the two files from a PKCS#12 file using OpenSSL: \n "
" openssl pkcs12 -in certfile.p12 -out certfile.pem -nodes -clcerts \n "
" openssl pkcs12 -in certfile.p12 -out keyfile.pem -nocerts \n " ;
}
QString command ( ) const {
return tag ( ) + " " + _certfile + " " + _keyfile + " " + _password ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < ClientCertificate > cmd ( new ( ClientCertificate ) ) ;
QStringList s ( args . trimmed ( ) . split ( ' ' ) ) ;
if ( s . size ( ) < 3 ) throw MissingArguments ( args , " certfile keyfile password " ) ;
cmd - > _certfile = s . takeFirst ( ) ;
cmd - > _keyfile = s . takeFirst ( ) ;
cmd - > _password = s . join ( ' ' ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
QSslConfiguration sslConfig ( QSslConfiguration : : defaultConfiguration ( ) ) ;
sslConfig . setProtocol ( QSsl : : AnyProtocol ) ;
sslConfig . setPeerVerifyMode ( QSslSocket : : AutoVerifyPeer ) ;
QString filename ( script - > replacevars ( _certfile ) ) ;
QFile certfile ( script - > path ( ) + filename ) ;
if ( ! certfile . exists ( ) ) certfile . setFileName ( filename ) ;
if ( ! certfile . exists ( ) | | ! certfile . open ( QIODevice : : ReadOnly ) )
error ( log , FileNotFound ( filename ) ) ;
QSslCertificate cert ( & certfile ) ;
if ( cert . isNull ( ) ) error ( log , NotACertificate ( filename ) ) ;
sslConfig . setLocalCertificate ( cert ) ;
filename = script - > replacevars ( _keyfile ) ;
QFile keyfile ( script - > path ( ) + filename ) ;
if ( ! keyfile . exists ( ) ) keyfile . setFileName ( filename ) ;
if ( ! keyfile . exists ( ) | | ! keyfile . open ( QIODevice : : ReadOnly ) )
error ( log , FileNotFound ( filename ) ) ;
keyfile . open ( QIODevice : : ReadOnly ) ;
QSslKey k ( & keyfile , QSsl : : Rsa , QSsl : : Pem ,
QSsl : : PrivateKey , script - > replacevars ( _password ) . toUtf8 ( ) ) ;
if ( k . isNull ( ) ) error ( log , KeyNotReadable ( filename ) ) ;
sslConfig . setPrivateKey ( k ) ;
QSslConfiguration : : setDefaultConfiguration ( sslConfig ) ;
return true ;
}
private :
QString _certfile ;
QString _keyfile ;
QString _password ;
} ;
class ClickType : public Command {
public :
QString tag ( ) const {
return " clicktype " ;
}
QString description ( ) const {
return
tag ( ) + " <type> "
" \n \n "
" Set how mouseclicks should be mapped. The best solution depends on "
" your problem. Normally it is good to call \" click() \" on the element "
" using javascript. But with complex javascript infected websites, it "
" might be necessary to simulate a real mouse click. \n \n "
" <type> is one of: \n "
" - realmouse \n "
" - javascript (default) " ;
}
QString command ( ) const {
switch ( _clicktype ) {
case Script : : REAL_MOUSE_CLICK : return tag ( ) + " realmouse " ;
case Script : : JAVASCRIPT_CLICK : default :
return tag ( ) + " javascript " ;
}
}
std : : shared_ptr < Command > parse ( Script * script , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < ClickType > cmd ( new ClickType ( ) ) ;
QString choice ( script - > replacevars ( args ) . trimmed ( ) ) ;
if ( choice = = " realmouse " )
cmd - > _clicktype = Script : : REAL_MOUSE_CLICK ;
else if ( choice = = " javascript " )
cmd - > _clicktype = Script : : JAVASCRIPT_CLICK ;
else
throw BadArgument ( " unknown clicktype: " + choice ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
script - > clicktype ( _clicktype ) ;
return true ;
}
private :
Script : : ClickType _clicktype ;
} ;
class SetValue : public Command {
public :
QString tag ( ) const {
return " setvalue " ;
}
QString description ( ) const {
return
tag ( ) + " <selector> -> '<value>' \n " +
tag ( ) + " <selector> -> '<value1>', '<value2>', <...> "
" \n \n "
" Set the selected element to a given value. It is mostly the same as "
" the following constuct, except that options in a select are evaluated "
" correctly: \n \n "
" do <selector> \n "
" this.value='<value>'; \n \n "
" The second syntax with comma separated list of valus applies for "
" options in a select element \n \n "
" If you quote the values, then quote all values with the same "
" quotes. If you need a comma within a value, you must quote. " ;
}
QString command ( ) const {
return tag ( ) + " " + _selector + " -> " + _value ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < SetValue > cmd ( new SetValue ( ) ) ;
QStringList allargs ( args . split ( " -> " ) ) ;
if ( allargs . size ( ) < 2 )
throw BadArgument ( tag ( ) + " requires <selector> -> <value>, "
" instead of: \" " + args + " \" " ) ;
cmd - > _selector = allargs . takeFirst ( ) . trimmed ( ) ;
cmd - > _value = allargs . join ( " -> " ) . trimmed ( ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QWebElement element ( find ( frame , script - > replacevars ( _selector ) ) ) ;
if ( element . isNull ( ) )
error ( log , ElementNotFound ( script - > replacevars ( _selector ) ) ) ;
QString value ( script - > replacevars ( _value ) ) ;
if ( element . tagName ( ) = = " SELECT " ) {
// value is a comma seperated list of option values
QStringList values ( commaSeparatedList ( value ) ) ;
Q_FOREACH ( QWebElement option , element . findAll ( " option " ) ) {
QString name ( option . evaluateJavaScript ( " this.value " ) . toString ( ) ) ;
option . evaluateJavaScript
( " this.selected= " + QString ( values . contains ( name ) ? " true; " : " false; " ) ) ;
}
} else {
if ( value . size ( ) > 1 & & value [ 0 ] = = value [ value . size ( ) - 1 ] & &
( value [ 0 ] = = ' " ' | | value [ 0 ] = = ' \' ' ) )
element . evaluateJavaScript ( " this.value= " + value + " ; " ) ;
else
element . evaluateJavaScript ( " this.value=' "
+ value . replace ( " ' " , " \\ ' " )
+ " '; " ) ;
}
return true ;
}
private :
QString _selector ;
QString _value ;
} ;
class Function : public Command {
public :
QString tag ( ) const {
return " function " ;
}
QString description ( ) const {
return
tag ( ) + " <name> [<var1>, <var2>, <...>] \n "
" <command1> \n "
" <command2> \n "
" <...> "
" \n \n "
" Define a function with arguments. The arguments are treated like "
" local variables. In a sequence of scripts within the same testrun, "
" functions are inherited from all followin scripts, so you can first "
" load a script file that contains all functions. Within the same file, "
" a function can be called before the definition. \n \n "
" If you quote the values, then quote all values with the same "
" quotes. If you need a comma within a value, you must quote. " ;
}
QString command ( ) const {
return tag ( ) + " " + _name + " " + _vars . join ( " , " ) ;
}
std : : shared_ptr < Command > parse ( Script * script , QString args ,
QStringList & in , QString file , int line ,
int indent ) {
std : : shared_ptr < Function > cmd ( new Function ( ) ) ;
if ( ! args . size ( ) ) throw BadArgument ( tag ( ) + " requires a <name> " ) ;
QStringList allargs ( args . split ( " " ) ) ;
cmd - > _name = allargs . takeFirst ( ) . trimmed ( ) ;
cmd - > _vars = commaSeparatedList ( allargs . join ( ' ' ) ) ;
script - > function ( cmd - > _name , cmd ) ;
cmd - > _script = std : : shared_ptr < Script > ( new Script ) ;
cmd - > _script - > parse ( subCommandBlock ( in ) , file , line + 1 , indent + 1 ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script , false ) ;
return true ;
}
bool call ( Logger & log , Command * parentCommand , QStringList args ,
Script * script , QWebFrame * frame ) {
try {
return runScript ( log , parentCommand , _script , script , frame , _vars , args ) ;
} catch ( const Exception & x ) {
error ( log , FunctionCallFailed ( _name , _vars , args , x ) ) ;
}
}
private :
QString _name ;
QStringList _vars ;
std : : shared_ptr < Script > _script ;
} ;
class Call : public Command {
public :
QString tag ( ) const {
return " call " ;
}
QString description ( ) const {
return
tag ( ) + " <name> ['<arg1>', '<arg2>', ...] "
" \n \n "
" Calls a function. The number of arguments must be exactly the same "
" as in the function definition. \n \n "
" If you quote the values, then quote all values with the same "
" quotes. If you need a comma within a value, you must quote. " ;
}
QString command ( ) const {
return tag ( ) + " " + _name + ( _args . size ( ) ? " ' " + _args . join ( " ', ' " ) + " ' " : " " ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Call > cmd ( new Call ( ) ) ;
if ( ! args . size ( ) ) throw BadArgument ( tag ( ) + " requires a <name> " ) ;
QStringList allargs ( args . split ( " " ) ) ;
cmd - > _name = allargs . takeFirst ( ) . trimmed ( ) ;
cmd - > _args = commaSeparatedList ( allargs . join ( ' ' ) ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
return script - > function ( log , _name ) - > call ( log , this , script - > replacevars ( _args ) ,
script , frame ) ;
}
public :
QString _name ;
QStringList _args ;
} ;
class If : public Command {
public :
QString tag ( ) const {
return " if " ;
}
QString description ( ) const {
return
tag ( ) + " <variable> <cmp> <value> \n "
" <command1> \n "
" <command2> \n "
" <...> \n "
" else \n "
" <command3> \n "
" <command4> \n "
" <...> \n "
" \n \n " +
tag ( ) + " <selector> -> <text> \n "
" <command1> \n "
" <command2> \n "
" <...> \n "
" else \n "
" <command3> \n "
" <command4> \n "
" <...> \n "
" \n \n "
" Execute commands conditionally. "
" The first variant compares a variable to a value. "
" The comparision <cmp> can be = ! . ^ ~ < >, "
" which means equal, different, contains, contains not, match, "
" less (as integer), bigger (as integer). "
" Match allows a regular expression. "
" The second variant checks for a text in a selector, "
" similar to command exists. The text can be empty to just "
" check for the existence of a selector. "
" There is an optional else part. " ;
}
QString command ( ) const {
return tag ( ) + " " + _variable + " " + _cmp + " " + _value
+ ( _script . get ( ) ? " \n " + _script - > print ( ) . join ( " \n " ) : " " ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & in , QString file , int line ,
int indent ) {
std : : shared_ptr < If > cmd ( new If ( ) ) ;
int pos ( args . indexOf ( QRegularExpression ( " [=!.^~<>] " ) ) ) ;
int len ( 1 ) ;
if ( args . contains ( " -> " ) ) {
pos = args . indexOf ( " -> " ) ;
len = 2 ;
cmd - > _cmp = " -> " ;
} else {
if ( pos < 0 ) throw BadArgument ( tag ( ) + " needs a comparision, not: " + args ) ;
cmd - > _cmp = args [ pos ] ;
}
cmd - > _variable = args . left ( pos ) . trimmed ( ) ;
cmd - > _value = args . mid ( pos + len ) . trimmed ( ) ;
cmd - > _script = std : : shared_ptr < Script > ( new Script ) ;
cmd - > _script - > parse ( subCommandBlock ( in ) , file , line + 1 , indent + 1 ) ;
if ( in . size ( ) & & in . first ( ) . contains ( QRegularExpression ( " ^ *else *$ " ) ) ) {
in . removeFirst ( ) ;
cmd - > _else = std : : shared_ptr < Script > ( new Script ) ;
cmd - > _else - > parse ( subCommandBlock ( in ) , file , line + 1 , indent + 1 ) ;
}
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script , false ) ;
QString value ( script - > replacevars ( _value ) ) ;
QString selector ( script - > replacevars ( _variable ) ) ;
bool check ( false ) ;
if ( _cmp = = " -> " ) {
Q_FOREACH ( QWebElement element , frame - > findAllElements ( selector ) ) {
if ( value . isEmpty ( ) | | // just find element
element . toOuterXml ( ) . indexOf ( value ) ! = - 1 | |
element . toPlainText ( ) . indexOf ( value ) ! = - 1 ) {
check = true ;
break ;
}
}
log ( QString ( " evaluated expression to " ) + ( check ? " true " : " false " ) + " : "
+ selector + " " + _cmp + " " + value ) ;
} else {
switch ( _cmp [ 0 ] . toLatin1 ( ) ) {
case ' = ' : check = script - > variable ( log , _variable ) = = value ;
break ;
case ' ! ' : check = script - > variable ( log , _variable ) ! = value ;
break ;
case ' . ' : check = script - > variable ( log , _variable ) . contains ( value ) ;
break ;
case ' ^ ' : check = ! script - > variable ( log , _variable ) . contains ( value ) ;
break ;
case ' ~ ' : check =
script - > variable ( log , _variable ) . contains ( QRegularExpression ( value ) ) ;
break ;
case ' < ' : check = script - > variable ( log , _variable ) . toInt ( ) < value . toInt ( ) ;
break ;
case ' > ' : check = script - > variable ( log , _variable ) . toInt ( ) > value . toInt ( ) ;
break ;
default : ;
}
log ( QString ( " evaluated expression to " ) + ( check ? " true " : " false " ) + " : "
+ script - > variable ( log , _variable ) + " " + _cmp + " " + value ) ;
}
if ( check ) return runScript ( log , this , _script , script , frame ) ;
else if ( _else ) return runScript ( log , this , _else , script , frame ) ;
return true ;
}
private :
QString _variable ;
QString _cmp ;
QString _value ;
std : : shared_ptr < Script > _script ;
std : : shared_ptr < Script > _else ;
} ;
class TestSuite : public Command {
public :
QString tag ( ) const {
return " testsuite " ;
}
QString description ( ) const {
return
tag ( ) + " <name> "
" \n \n "
" Start a testsuite and give it a name. " ;
}
QString command ( ) const {
return tag ( ) + " " + _name ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < TestSuite > cmd ( new TestSuite ( ) ) ;
cmd - > _name = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
script - > testsuite ( script - > replacevars ( _name ) ) ;
return true ;
}
private :
QString _name ;
} ;
class TestCase : public Command {
public :
QString tag ( ) const {
return " testcase " ;
}
QString description ( ) const {
return
tag ( ) + " <name> "
" \n \n "
" Start a testcase and give it a name. " ;
}
QString command ( ) const {
return tag ( ) + " " + _name ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < TestCase > cmd ( new TestCase ( ) ) ;
cmd - > _name = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
script - > testclass ( script - > replacevars ( _name ) ) ;
return true ;
}
private :
QString _name ;
} ;
class Check : public Command {
public :
QString tag ( ) const {
return " check " ;
}
QString description ( ) const {
return
tag ( ) + " <value1> <cmp> <value2> \n " +
tag ( ) + " <value1> <cmp> \n "
" <command> "
" \n \n "
" Compares two values (you can use variables) or compares a value to the "
" result of a command. The command should be a command that produces an "
" output, such as <do>, which returns the result of JavaScript or "
" <execute>, which returns the output of the executed command, or "
" <call>, which returns the result of the last command. "
" The comparision <cmp> can be = ! . ^ ~ < >, "
" which means equal, different, contains, contains not, match, "
" less (as integer), bigger (as integer). "
" Match allows a regular expression. "
" less than < (integers), larger than > (integers) " ;
}
QString command ( ) const {
if ( _next )
return tag ( ) + " " + _value1 + " " + QString ( _cmp ) + " \n " + _next - > command ( ) ;
else
return tag ( ) + " " + _value1 + " " + QString ( _cmp ) + " " + _value2 ;
}
std : : shared_ptr < Command > parse ( Script * script , QString args ,
QStringList & in , QString file , int line ,
int indent ) {
std : : shared_ptr < Check > cmd ( new Check ( ) ) ;
cmd - > _next = 0 ;
int pos ( args . indexOf ( QRegularExpression ( " [=!.^~<>] " ) ) ) ;
if ( pos < 0 ) throw BadArgument ( tag ( ) + " needs a comparision, not: " + args ) ;
cmd - > _value1 = args . left ( pos ) . trimmed ( ) ;
cmd - > _cmp = args [ pos ] . toLatin1 ( ) ;
cmd - > _value2 = args . mid ( pos + 1 ) . trimmed ( ) ;
if ( in . size ( ) & & in . first ( ) . contains ( QRegularExpression ( " ^ " ) ) ) {
cmd - > _next = script - > parseLine ( in , file , line + 1 , indent + 1 ) ;
cmd - > _next - > log ( false ) ; // suppress logging of subcommand
}
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QString value1 ( script - > replacevars ( _value1 ) ) ;
QString value2 ( script - > replacevars ( _value2 ) ) ;
if ( _next ) {
_next - > execute ( script , frame ) ;
value2 = script - > replacevars ( _next - > result ( ) ) ;
}
bool check ( false ) ;
switch ( _cmp ) {
case ' = ' : check = value1 = = value2 ; break ;
case ' ! ' : check = value1 ! = value2 ; break ;
case ' . ' : check = value1 . contains ( value2 ) ; break ;
case ' ^ ' : check = ! value1 . contains ( value2 ) ; break ;
case ' ~ ' : check = value1 . contains ( QRegularExpression ( value2 ) ) ; break ;
case ' < ' : check = value1 . toInt ( ) < value2 . toInt ( ) ; break ;
case ' > ' : check = value1 . toInt ( ) > value2 . toInt ( ) ; break ;
default : ;
}
log ( " evaluated expression: " + value1 + " " + _cmp + " " + value2 ) ;
if ( ! check ) error ( log , CheckFailed ( value1 , _cmp , value2 ) ) ;
return true ;
}
private :
QString _value1 ;
QString _value2 ;
char _cmp ;
std : : shared_ptr < Command > _next ;
} ;
class For : public Command {
public :
QString tag ( ) const {
return " for " ;
}
QString description ( ) const {
return
tag ( ) + " <variable> -> <val1>, <val2>, <...> \n "
" <command1> \n "
" <command2> \n "
" <...> "
" \n \n "
" Executes the given commands with the variable set to the specifier values, "
" repeated once per given value. The variable is treated like a local variale "
" in the loop. \n \n "
" Without values, if there is a global variable with the same name as the "
" local variable the global variable is parsed as if it were the line after "
" the dash (->). \n \n " ;
" If you quote the values, then quote all values with the same "
" quotes. If you need a comma within a value, you must quote. " ;
}
QString command ( ) const {
return tag ( ) + " " + _variable + " " + _vals . join ( " " ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & in , QString file , int line , int indent ) {
std : : shared_ptr < For > cmd ( new For ( ) ) ;
if ( ! args . size ( ) ) throw BadArgument ( tag ( ) + " requires a <variable> " ) ;
QStringList allargs ( args . split ( " -> " ) ) ;
cmd - > _variable = allargs . takeFirst ( ) . trimmed ( ) ;
cmd - > _vals = commaSeparatedList ( allargs . join ( " -> " ) ) ;
cmd - > _script = std : : shared_ptr < Script > ( new Script ) ;
cmd - > _script - > parse ( subCommandBlock ( in ) , file , line + 1 , indent + 1 ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
Q_FOREACH ( QString i , _vals . size ( ) ? _vals : commaSeparatedList ( script - > variable ( log , _variable ) ) ) {
if ( ! runScript ( log , this , _script , script , frame , QStringList ( ) < < _variable , QStringList ( ) < < i ) )
return false ;
}
return true ;
}
private :
QString _variable ;
QStringList _vals ;
std : : shared_ptr < Script > _script ;
} ;
class Echo : public Command {
public :
QString tag ( ) const {
return " echo " ;
}
QString description ( ) const {
return
tag ( ) + " <text> "
" \n \n "
" Echoes a text to the log. " ;
}
QString command ( ) const {
return tag ( ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Echo > cmd ( new ( Echo ) ) ;
cmd - > _text = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
log ( script - > replacevars ( _text ) ) ;
return true ;
}
private :
QString _text ;
} ;
class OfflineStoragePath : public Command {
public :
QString tag ( ) const {
return " offline-storage-path " ;
}
QString description ( ) const {
return
tag ( ) + " <path> "
" \n \n "
" Set offline storage path. Defaults to /tmp. " ;
}
QString command ( ) const {
return tag ( ) + " " + _path ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < OfflineStoragePath > cmd ( new OfflineStoragePath ( ) ) ;
cmd - > _path = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QDir path ( _path ) ;
if ( ! path . exists ( ) & & ! path . mkpath ( _path ) )
error ( log , DirectoryCannotBeCreated ( _path ) ) ;
TestWebPage * page ( dynamic_cast < TestWebPage * > ( frame - > page ( ) ) ) ;
page - > settings ( ) - > setOfflineStoragePath ( _path ) ;
return true ;
}
private :
QString _path ;
} ;
class ClearCookies : public Command {
public :
QString tag ( ) const {
return " clear-cookies " ;
}
QString description ( ) const {
return
tag ( ) + " <url> "
" \n \n "
" Clear all cookies of given URL <url>, or of the current URL if no "
" <url> is specified. " ;
}
QString command ( ) const {
return tag ( ) + ( _url . isEmpty ( ) ? " " : " " + _url ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < ClearCookies > cmd ( new ClearCookies ( ) ) ;
if ( args . size ( ) ) cmd - > _url = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QString url ( _url ) ;
if ( url . isEmpty ( ) ) url = frame - > url ( ) . toString ( ) ;
QNetworkCookieJar * cookies = frame - > page ( ) - > networkAccessManager ( ) - > cookieJar ( ) ;
Q_FOREACH ( QNetworkCookie cookie , cookies - > cookiesForUrl ( url ) ) {
log ( " delete cookie " + cookie . name ( ) ) ;
cookies - > deleteCookie ( cookie ) ;
}
return true ;
}
private :
QString _url ;
} ;
class Include : public Command {
public :
QString tag ( ) const {
return " include " ;
}
QString description ( ) const {
return
tag ( ) + " <filename> "
" \n \n "
" Include a test script. " ;
}
QString command ( ) const {
return tag ( ) + " " + _filename ;
}
std : : shared_ptr < Command > parse ( Script * script , QString args ,
QStringList & , QString , int , int indent ) {
std : : shared_ptr < Include > cmd ( new Include ( ) ) ;
cmd - > _filename = args ;
QFile f ( cmd - > _filename ) ;
if ( ! f . open ( QIODevice : : ReadOnly ) ) throw OpenIncludeFailed ( cmd - > _filename ) ;
QString txt ( QString : : fromUtf8 ( f . readAll ( ) ) ) ;
try {
cmd - > _script = std : : shared_ptr < Script > ( new Script ) ;
cmd - > _script - > parse ( txt . split ( ' \n ' ) , cmd - > _filename , 1 , indent + 1 ) ;
} catch ( Exception & e ) {
throw ParseIncludeFailed ( cmd - > _filename , e . what ( ) ) ;
}
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
try {
return runScript ( log , this , _script , script , frame ) ;
} catch ( Exception & e ) {
error ( log , ExecuteIncludeFailed ( _filename , e . what ( ) ) ) ;
}
}
private :
QString _filename ;
std : : shared_ptr < Script > _script ;
} ;
class Case : public Command {
public :
QString tag ( ) const {
return " case " ;
}
QString description ( ) const {
return
tag ( ) + " <variable> \n "
" <cmp1> <value1> \n "
" <command> \n "
" <command> \n "
" <cmp1> <value2> \n "
" <command> \n "
" <command> \n "
" <...> \n "
" default \n "
" <command> \n "
" <command> \n "
" <...> \n "
" \n \n " +
tag ( ) + " <selector> \n "
" -> <text1> \n "
" <command> \n "
" <command> \n "
" -> <text2> \n "
" <command> \n "
" <command> \n "
" <...> \n "
" default \n "
" <command> \n "
" <command> \n "
" <...> \n "
" \n \n "
" Execute commands conditionally depending on a variable. "
" It is equivalent to neested if-else-if commands. "
" The first variant compares a variable to a value. "
" The comparision <cmp> can be = ! . ^ ~ < >, "
" which means equal, different, contains, contains not, match, "
" less (as integer), bigger (as integer). "
" Match allows a regular expression. "
" The second variant checks for a text in a selector, "
" similar to command exists. "
" There is an optional default part that applies if none "
" of the previous conditions match. " ;
}
QString command ( ) const {
QString body ;
Q_FOREACH ( Condition condition , _conditions ) {
body + = " \n " + condition . cmp + " " + condition . value + " \n "
+ condition . script - > print ( ) . join ( " \n " ) ;
}
return tag ( ) + " " + _variable + body ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & in , QString file , int line ,
int indent ) {
std : : shared_ptr < Case > cmd ( new Case ( ) ) ;
if ( ! args . size ( ) ) throw BadArgument ( tag ( ) + " requires a <variable> or <selector> " ) ;
cmd - > _variable = args ;
QStringList body ( subCommandBlock ( in ) ) ;
while ( body . size ( ) ) {
+ + line ;
QStringList parts ( body . takeFirst ( ) . split ( ' ' ) ) ;
QString cmp ( parts . takeFirst ( ) ) ;
QString value ( parts . join ( ' ' ) ) ;
if ( ! cmp . contains ( QRegularExpression ( " ^[=!.^~<>]|->|default$ " ) ) )
throw BadArgument ( tag ( ) + " needs a comparision, not: " + cmp ) ;
std : : shared_ptr < Script > script ( std : : shared_ptr < Script > ( new Script ) ) ;
script - > parse ( subCommandBlock ( body ) , file , line + 1 , indent + 2 ) ;
cmd - > _conditions . append ( Condition ( cmp , value , script ) ) ;
}
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script , false ) ;
QString selector ( script - > replacevars ( _variable ) ) ;
Q_FOREACH ( Condition condition , _conditions ) {
QString value ( script - > replacevars ( condition . value ) ) ;
bool check ( false ) ;
if ( condition . cmp = = " default " ) {
log ( " terminate with default branch " ) ;
check = true ;
} else if ( condition . cmp = = " -> " ) {
Q_FOREACH ( QWebElement element , frame - > findAllElements ( selector ) ) {
if ( value . isEmpty ( ) | | // just find element
element . toOuterXml ( ) . indexOf ( value ) ! = - 1 | |
element . toPlainText ( ) . indexOf ( value ) ! = - 1 ) {
check = true ;
break ;
}
}
log ( QString ( " evaluated expression to " ) + ( check ? " true " : " false " ) + " : "
+ selector + " " + condition . cmp + " " + value ) ;
} else {
switch ( condition . cmp [ 0 ] . toLatin1 ( ) ) {
case ' = ' : check = script - > variable ( log , _variable ) = = value ;
break ;
case ' ! ' : check = script - > variable ( log , _variable ) ! = value ;
break ;
case ' . ' : check = script - > variable ( log , _variable ) . contains ( value ) ;
break ;
case ' ^ ' : check = ! script - > variable ( log , _variable ) . contains ( value ) ;
break ;
case ' ~ ' : check =
script - > variable ( log , _variable ) . contains ( QRegularExpression ( value ) ) ;
break ;
case ' < ' : check = script - > variable ( log , _variable ) . toInt ( ) < value . toInt ( ) ;
break ;
case ' > ' : check = script - > variable ( log , _variable ) . toInt ( ) > value . toInt ( ) ;
break ;
default : ;
}
log ( QString ( " evaluated expression to " ) + ( check ? " true " : " false " ) + " : "
+ script - > variable ( log , _variable ) + " " + condition . cmp + " " + value ) ;
}
if ( check ) return runScript ( log , this , condition . script , script , frame ) ;
}
return true ;
}
private :
struct Condition {
Condition ( QString c , QString v , std : : shared_ptr < Script > s ) :
cmp ( c ) , value ( v ) , script ( s ) {
}
Condition ( ) { }
QString cmp ;
QString value ;
std : : shared_ptr < Script > script ;
} ;
QString _variable ;
QVector < Condition > _conditions ;
} ;
class Fail : public Command {
public :
QString tag ( ) const {
return " fail " ;
}
QString description ( ) const {
return
tag ( ) + " <text> "
" \n \n "
" Fail with error text. " ;
}
QString command ( ) const {
return tag ( ) + " " + _text ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < Fail > cmd ( new Fail ( ) ) ;
cmd - > _text = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
error ( log , TestFailed ( script - > replacevars ( _text ) ) ) ;
return true ; // dummy
}
private :
QString _text ;
} ;
/* Template:
class : public Command {
public :
QString tag ( ) const {
return " " ;
}
QString description ( ) const {
return
tag ( ) +
" \n \n "
" " ;
}
QString command ( ) const {
return tag ( ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , QString , int , int ) {
std : : shared_ptr < > cmd ( new ( ) ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
return true ;
}
} ;
*/
inline bool Screenshot : : execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
if ( ! script - > screenshots ( ) ) {
log ( " screenshots disabled " ) ;
return true ;
}
QString filename ( screenshot ( log , line ( ) , script - > targetdir ( ) ,
script - > testclass ( ) ,
script - > replacevars ( _filename ) , frame ) ) ;
log [ " [[ATTACHMENT| " + filename + " ]] " ] ;
return true ;
}
inline Logger : : Logger ( Command * command , Script * script , bool showLines ) :
_command ( command ) , _script ( script ) {
if ( command ) {
_previous = _script - > command ( ) ;
_script - > command ( command ) ;
if ( _command - > log ( ) )
if ( showLines )
_script - > log ( " \\ " + _command - > command ( ) , _command ) ;
else
_script - > log ( " \\ " + _command - > command ( ) . split ( ' \n ' ) . first ( ) , _command ) ;
}
}
inline void Logger : : operator ( ) ( QString txt ) {
if ( ! _command | | _command - > log ( ) ) _script - > log ( " " + txt , _command ) ;
}
inline void Logger : : operator [ ] ( QString txt ) {
_script - > plainlog ( txt ) ;
}
inline Logger : : ~ Logger ( ) {
if ( _command ) {
if ( _command - > log ( ) ) _script - > log ( " / " + _command - > tag ( ) , _command ) ;
_script - > command ( _previous ) ;
}
}
inline bool Command : : runScript ( Logger & log , Command * parentCommand ,
std : : shared_ptr < Script > script ,
Script * parent , QWebFrame * frame ,
QStringList vars ,
QStringList args ) {
Script scriptCopy ( * script ) ; // only work with a copy of script
scriptCopy . set ( * parent ) ;
if ( args . size ( ) ! = vars . size ( ) )
error ( log , WrongNumberOfArguments ( vars , args ) ) ;
for ( QStringList : : iterator var ( vars . begin ( ) ) , arg ( args . begin ( ) ) ;
var < vars . end ( ) & & arg < args . end ( ) ; + + var , + + arg ) {
parent - > log ( " argument: " + * var + " = " + parent - > replacevars ( * arg ) ,
parentCommand ) ;
scriptCopy . set ( * var , parent - > replacevars ( * arg ) ) ;
}
try {
connect ( & scriptCopy , SIGNAL ( logging ( QString ) ) ,
parent , SLOT ( parentlog ( QString ) ) ) ;
parent - > removeSignals ( frame ) ;
bool res ( scriptCopy . run ( frame ) ) ;
parent - > addSignals ( frame ) ;
disconnect ( & scriptCopy , SIGNAL ( logging ( QString ) ) ,
parent , SLOT ( parentlog ( QString ) ) ) ;
parentCommand - > _result = scriptCopy . result ( ) ;
Q_FOREACH ( QString key , scriptCopy . variables ( ) ) // copy new variables to parent
if ( ! vars . contains ( key ) ) parent - > set ( key , scriptCopy . variable ( log , key ) ) ;
Q_FOREACH ( QString key , scriptCopy . functions ( ) ) // copy new functions to parent
parent - > function ( key , scriptCopy . function ( log , key ) ) ;
if ( parentCommand - > _result . size ( ) )
parent - > log ( " result: " + parentCommand - > _result ) ;
return res ;
} catch ( const Exception & x ) {
parent - > addSignals ( frame ) ;
disconnect ( & scriptCopy , SIGNAL ( logging ( QString ) ) ,
parent , SLOT ( parentlog ( QString ) ) ) ;
throw ;
}
}
inline void Script : : initPrototypes ( ) {
add ( new Do ) ;
add ( new Load ) ;
add ( new Expect ) ;
add ( new Screenshot ) ;
add ( new Open ) ;
add ( new Sleep ) ;
add ( new Exit ) ;
add ( new IgnoreTo ) ;
add ( new Label ) ;
add ( new Upload ) ;
add ( new Exists ) ;
add ( new Not ) ;
add ( new Execute ) ;
add ( new Download ) ;
add ( new Click ) ;
add ( new Set ) ;
add ( new UnSet ) ;
add ( new Timeout ) ;
add ( new CaCertificate ) ;
add ( new ClientCertificate ) ;
add ( new : : ClickType ) ;
add ( new SetValue ) ;
add ( new Function ) ;
add ( new Call ) ;
add ( new If ) ;
add ( new TestSuite ) ;
add ( new TestCase ) ;
add ( new Check ) ;
add ( new For ) ;
add ( new Echo ) ;
add ( new OfflineStoragePath ) ;
add ( new ClearCookies ) ;
add ( new Include ) ;
add ( new Case ) ;
add ( new Fail ) ;
}
# endif