/*! @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 <vector>
# include <queue>
# include <map>
# include <memory>
# include <sstream>
# include <cassert>
# include <xml-cxx/xml.hxx>
class Script ;
class Command ;
class Logger {
public :
Logger ( Command * command , Script * script ) ;
void plainlog ( QString txt ) ;
void log ( QString txt ) ;
~ Logger ( ) ;
private :
Command * _command ;
Script * _script ;
} ;
class Command : public QObject {
Q_OBJECT ;
public :
Command ( ) : _log ( true ) , _line ( - 1 ) { }
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 & , int ) = 0 ;
virtual bool execute ( Script * , QWebFrame * ) = 0 ;
void line ( int linenr ) {
_line = linenr ;
}
int line ( ) const {
return _line ;
}
void testsuite ( QString name ) {
_testsuite = name ;
}
QString testsuite ( ) {
return _testsuite ;
}
void targetdir ( QString name ) {
_targetdir = name ;
}
QString targetdir ( ) {
return _targetdir ;
}
bool log ( ) {
return _log ;
}
void log ( bool l ) {
_log = l ;
}
QString result ( ) {
return _result ;
}
static void realMouseClick ( QWebFrame * frame , QString selector ) {
QWebElement element ( find ( frame , selector ) ) ;
if ( element . isNull ( ) ) throw 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 ) ;
}
public :
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 _testsuite ;
QString _targetdir ;
} ;
class Empty : public Command {
public :
QString tag ( ) const {
return " " ;
}
QString description ( ) const {
return
" "
" \n \n "
" Empty lines are allowed " ;
}
QString command ( ) const {
return " " ;
}
std : : shared_ptr < Command > parse ( Script * , QString , QStringList & , int ) {
std : : shared_ptr < Empty > cmd ( new Empty ( ) ) ;
return cmd ;
}
bool execute ( Script * , QWebFrame * ) {
return true ;
}
} ;
class Comment : public Command {
public :
Comment ( QString line ) : _line ( line ) { }
QString tag ( ) const {
return " # " ;
}
QString description ( ) const {
return
" # comment "
" \n \n "
" Comments are lines that start with # " ;
}
QString command ( ) const {
return _line ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , int ) {
std : : shared_ptr < Comment > cmd ( new Comment ( args ) ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * ) {
Logger log ( this , script ) ;
return true ;
}
private :
QString _line ;
} ;
class Screenshot : public Command {
public :
static QString sourceHtml ( int line , QString target , QString base ,
QString name , QWebFrame * frame ) {
if ( ! QDir ( target ) . exists ( ) & & ! QDir ( ) . mkpath ( target ) )
throw 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 ) )
throw CannotWriteSouceHTML ( filename ) ;
QTextStream out ( & file ) ;
out < < frame - > toHtml ( ) ;
if ( out . status ( ) ! = QTextStream : : Ok ) throw CannotWriteSouceHTML ( filename ) ;
return QDir ( filename ) . absolutePath ( ) ;
}
static QString screenshot ( 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 ) )
throw 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 ) ) throw CannotWriteScreenshot ( filename ) ;
return QDir ( filename ) . absolutePath ( ) ;
}
QString tag ( ) const {
return " screenshot " ;
}
QString description ( ) const {
return
" screenshot <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 " screenshot " + _filename ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , 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 ) ;
public :
typedef std : : pair < QString , QStringList > Signal ;
public :
static QString xmlattr ( QString attr , bool br = false ) {
attr . replace ( " & " , " & " ) //.replace(" ", " ")
. replace ( " \" " , " " " ) ;
if ( br ) attr . replace ( " \n " , " <br/> " ) ;
attr . replace ( " < " , " < " ) . replace ( " > " , " > " ) ;
return attr ;
}
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 ( ) {
initPrototypes ( ) ;
}
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 "
" Note: When a selector is required as parameter, then the selector "
" is a CSS selector that must not contain spaces. " ;
}
QString commands ( ) const {
QString cmds ;
for ( auto it ( _prototypes . begin ( ) ) ; it ! = _prototypes . end ( ) ; + + it )
cmds + = " \n \n " + it - > second - > description ( ) ;
return cmds . trimmed ( ) ;
}
void reset ( ) {
_script . clear ( ) ;
while ( ! _signals . empty ( ) ) _signals . pop ( ) ;
_timer . stop ( ) ;
_ignores . clear ( ) ;
_cout . clear ( ) ;
_cerr . clear ( ) ;
}
std : : shared_ptr < Command > parse ( QStringList & in , int linenr ) try {
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 ( ) ) {
std : : shared_ptr < Command > command ( it - > second - > parse
( this , args , in , linenr ) ) ;
command - > line ( linenr ) ;
return command ;
} else {
return unknown ( line ) ;
}
} catch ( Exception & e ) {
e . line ( linenr ) ;
throw ;
}
void parse ( QStringList in ) {
for ( int linenr ( 1 ) , oldsize ( 0 ) ;
oldsize = in . size ( ) , in . size ( ) ;
linenr + = oldsize - in . size ( ) )
_script . push_back ( parse ( in , linenr ) ) ;
}
void run ( QWebFrame * frame , xml : : Node & testsuite ,
QString targetdir = QString ( ) , bool screenshots = true ,
int maxretries = 0 ) {
_timeout = 20 ; // defaults to 20s
_ignoreSignalsUntil . clear ( ) ;
addSignals ( frame ) ;
_screenshots = screenshots ;
_timer . setSingleShot ( true ) ;
int retries ( 0 ) , back ( 0 ) ;
for ( auto cmd ( _script . begin ( ) ) ; cmd ! = _script . end ( ) ; + + cmd ) {
xml : : Node testcase ( " testcase " ) ;
try {
testcase . attr ( " classname " ) =
testsuite . attr ( " name " ) ;
//xmlattr((*cmd)->command(), true).toStdString();
testcase . attr ( " name " ) =
xmlattr ( ( * cmd ) - > tag ( ) , true ) . toStdString ( ) ;
if ( ! _ignores . size ( ) | | ( * cmd ) - > tag ( ) = = " label " ) { // not ignored
_timer . start ( _timeout * 1000 ) ;
( * cmd ) - > testsuite ( xmlstr ( testsuite . attr ( " name " ) ) ) ;
( * cmd ) - > targetdir ( ! targetdir . isEmpty ( ) ? targetdir :
xmlstr ( testsuite . attr ( " name " ) ) ) ;
try {
if ( ! ( * 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 ( ) ;
testsuite < < testcase ;
break ; // test is successfully finished
}
} catch ( PossibleRetryLoad & e ) {
_timer . stop ( ) ;
// timeout may happen during load due to bad internet connection
if ( screenshots )
try { // take a screenshot on error
QString filename ( Screenshot : : screenshot
( ( * cmd ) - > line ( ) , ( * cmd ) - > targetdir ( ) ,
QFileInfo ( ( * cmd ) - > testsuite ( ) ) . baseName ( ) ,
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 ;
back + = 3 ;
_ignoreSignalsUntil = " loadStarted " ;
frame - > load ( url ) ;
} else if ( ( * cmd ) - > command ( ) = = " expect loadStarted " ) {
- - - - cmd ;
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 ;
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 ;
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 ( ) ;
testsuite < < 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 ( ) ;
testsuite < < testcase ;
removeSignals ( frame ) ;
e . line ( ( * cmd ) - > line ( ) ) ;
if ( screenshots )
try { // write html source and take a last screenshot on error
{
QString filename ( Screenshot : : sourceHtml
( ( * cmd ) - > line ( ) , ( * cmd ) - > targetdir ( ) ,
QFileInfo ( ( * cmd ) - > testsuite ( ) ) . baseName ( ) ,
" error " , frame ) ) ;
plainlog ( " [[ATTACHMENT| " + filename + " ]] " ) ;
} {
QString filename ( Screenshot : : screenshot
( ( * cmd ) - > line ( ) , ( * cmd ) - > targetdir ( ) ,
QFileInfo ( ( * cmd ) - > testsuite ( ) ) . baseName ( ) ,
" error " , frame ) ) ;
plainlog ( " [[ATTACHMENT| " + filename + " ]] " ) ;
}
} catch ( . . . ) { } // ignore exception in screenshot
throw ;
}
}
removeSignals ( frame ) ;
if ( ! _signals . empty ( ) ) throw UnhandledSignals ( _signals ) ;
}
QString & cout ( ) {
return _cout ;
}
QString & cerr ( ) {
return _cerr ;
}
int steps ( ) {
return _script . size ( ) ;
}
bool screenshots ( ) {
return _screenshots ;
}
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 ;
}
void unset ( QString name ) {
_variables . remove ( name ) ;
}
void timeout ( int t ) {
_timeout = t ;
}
QString replacevars ( QString txt ) {
for ( QMap < QString , QString > : : iterator it ( _variables . begin ( ) ) ;
it ! = _variables . end ( ) ; + + it )
txt . replace ( it . key ( ) , it . value ( ) ) ;
return txt ;
}
public Q_SLOTS :
void log ( QString text ) {
text = QDateTime : : currentDateTime ( ) . toString ( " yyyy-MM-dd HH:mm:ss " ) + text ;
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 " ;
}
private :
std : : shared_ptr < Command > unknown ( QString line ) {
if ( ! line . size ( ) ) return std : : shared_ptr < Command > ( new Empty ( ) ) ;
if ( line [ 0 ] = = ' # ' ) return std : : shared_ptr < Command > ( new Comment ( line ) ) ;
throw UnknownCommand ( line ) ; // error
}
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 ( ) ) ) ;
}
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 " ) ;
log ( " received loadFinished " + QString ( ok ? " true " : " false " ) ) ;
log ( " ..................... " ) ;
_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 " ) ;
log ( " received loadStarted " ) ;
log ( " ..................... " ) ;
_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 " ) ;
log ( " received urlChanged " + url . toString ( ) ) ;
log ( " ..................... " ) ;
_signals . push ( std : : make_pair ( " urlChanged " ,
QStringList ( url . toString ( ) ) ) ) ;
}
void timeout ( ) {
throw 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 ;
QString _ignoreSignalsUntil ;
QMap < QString , QString > _variables ;
int _timeout ;
} ;
class Do : public Command {
public :
QString tag ( ) const {
return " do " ;
}
QString description ( ) const {
return
" do <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 " do " + _selector + _javascript ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & in , int ) {
std : : shared_ptr < Do > cmd ( new Do ( ) ) ;
cmd - > _selector = args ;
while ( in . size ( ) & & in [ 0 ] . size ( ) & & in [ 0 ] [ 0 ] = = ' ' )
cmd - > _javascript + = " \n " + in . takeFirst ( ) ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
QWebElement element ( find ( frame , _selector ) ) ;
if ( element . isNull ( ) ) throw ElementNotFound ( _selector ) ;
_result =
element . evaluateJavaScript ( script - > replacevars ( _javascript ) ) . toString ( ) ;
return true ;
}
private :
QString _selector ;
QString _javascript ;
} ;
class Load : public Command {
public :
QString tag ( ) const {
return " load " ;
}
QString description ( ) const {
return
" load <url> "
" \n \n "
" Load an URL, the URL is given as parameter in full syntax. " ;
}
QString command ( ) const {
return " load " + _url ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , 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
" expect <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 \n "
" - urlChanged <url> \n "
" - loadFinished true " ;
}
QString command ( ) const {
return " expect " + _signal . _signal
+ ( _signal . _args . size ( ) ? " " + _signal . _args . join ( ' ' ) : QString ( ) ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , 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 ) ;
QList < Signal > sigs ;
if ( _signal . _signal = = " load " ) { // special signal load
sigs + = Signal ( " loadStarted " ) ;
sigs + = Signal ( " urlChanged " , _signal . _args ) ;
sigs + = Signal ( " loadFinished " , " true " ) ;
} else {
sigs + = _signal ;
}
Q_FOREACH ( Signal signal , sigs ) {
QStringList args ;
Q_FOREACH ( QString arg , signal . _args )
args . push_back ( script - > replacevars ( arg ) ) ;
Script : : Signal lastsignal ( script - > getSignal ( ) ) ;
QStringList lastargs ;
Q_FOREACH ( QString arg , lastsignal . second )
lastargs . push_back ( script - > replacevars ( arg ) ) ;
if ( lastsignal . first ! = signal . _signal | | ( args . size ( ) & & args ! = lastargs ) )
throw 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
" open "
" \n \n "
" Open the browser window, so you can follow the test steps visually. " ;
}
QString command ( ) const {
return " open " ;
}
std : : shared_ptr < Command > parse ( Script * , QString , QStringList & , 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
" sleep <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 " sleep " + _time ;
}
std : : shared_ptr < Command > parse ( Script * , QString time , QStringList & , 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 )
throw 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
" exit "
" \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 " exit " ;
}
std : : shared_ptr < Command > parse ( Script * , QString , QStringList & , 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
" ignoreto <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 " ignoreto " + _label ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , 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
" label <label> "
" \n \n "
" This marks the label refered by command \" ignoreto \" . " ;
}
QString command ( ) const {
return " label " + _label ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , 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
" upload <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 " upload " + _selector + " -> " + _filename ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , int ) {
std : : shared_ptr < Upload > cmd ( new Upload ( ) ) ;
QStringList allargs ( args . split ( " -> " ) ) ;
if ( allargs . size ( ) < 2 )
throw BadArgument ( " upload needs a selector folowed by a 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 - > 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 ( frame , script - > replacevars ( _selector ) ) ;
if ( page - > uploadPrepared ( ) )
throw 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
" exists <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 " exists " + _selector + ( _text . size ( ) ? " -> " + _text : QString ( ) ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , 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 ) ) ;
Q_FOREACH ( QWebElement element , frame - > findAllElements ( selector ) ) {
if ( text . isEmpty ( ) ) return true ; // just find element
if ( element . toOuterXml ( ) . indexOf ( text ) ! = - 1 ) return true ;
}
QWebElement element ( find ( frame , selector ) ) ;
if ( text . isEmpty ( ) )
throw AssertionFailed ( " element not found: " + selector ) ;
else if ( element . isNull ( ) )
throw AssertionFailed ( " expected \" " + text + " \" in non existing element "
+ selector ) ;
else
throw AssertionFailed ( " not found \" " + text + " \" in \" "
+ element . toOuterXml ( ) + " \" on " + selector ) ;
return true ; // never reached due to throw above
}
private :
QString _selector ;
QString _text ;
} ;
class Not : public Command {
public :
QString tag ( ) const {
return " not " ;
}
QString description ( ) const {
return
" not <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 " not " + _selector + ( _text . size ( ) ? " -> " + _text : QString ( ) ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , 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 ( ) )
throw AssertionFailed ( " element must not exists: " + selector ) ;
if ( element . toOuterXml ( ) . indexOf ( text ) ! = - 1 )
throw 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
" execute <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 ( QRegExp ( " ^ " ) , " " ) ;
return " execute " + _command
+ ( _args . size ( ) ? " " + _args . join ( ' ' ) : QString ( ) )
+ ( script . size ( ) ? " \n " + script . join ( " \n " ) : QString ( ) ) ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & in , int ) {
std : : shared_ptr < Execute > cmd ( new Execute ( ) ) ;
cmd - > _args = args . split ( ' ' ) ;
cmd - > _command = cmd - > _args . takeFirst ( ) ;
int pos ( - 1 ) ;
while ( in . size ( ) & & in [ 0 ] . size ( ) & & in [ 0 ] [ 0 ] = = ' ' ) {
if ( pos < 0 ) pos = in [ 0 ] . toStdString ( ) . find_first_not_of ( ' ' ) ;
cmd - > _script + = in . takeFirst ( ) . mid ( pos ) ;
}
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 ( ) )
throw CannotStartScript ( command , args , scripttxt ) ;
if ( scripttxt . size ( ) ) {
if ( exec . write ( scripttxt . toUtf8 ( ) ) ! = scripttxt . toUtf8 ( ) . size ( ) | |
! exec . waitForBytesWritten ( 60000 ) )
throw CannotLoadScript ( command , args , scripttxt ) ;
}
exec . closeWriteChannel ( ) ;
if ( ! exec . waitForFinished ( 60000 ) & & exec . state ( ) ! = QProcess : : NotRunning )
throw ScriptNotFinished ( command , args , scripttxt ) ;
QString stdout ( exec . readAllStandardOutput ( ) ) ;
QString stderr ( exec . readAllStandardError ( ) ) ;
_result = stdout ;
script - > log ( stdout ) ;
if ( exec . exitCode ( ) ! = 0 | | exec . exitStatus ( ) ! = QProcess : : NormalExit )
throw 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
" download <filename> "
" <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. " ;
}
QString command ( ) const {
return " download " + ( _filename . size ( ) ? " " + _filename : QString ( ) ) + " \n "
+ _next - > command ( ) ;
}
std : : shared_ptr < Command > parse ( Script * script , QString args ,
QStringList & in , int line ) {
std : : shared_ptr < Download > cmd ( new Download ( ) ) ;
cmd - > _filename = args . trimmed ( ) ;
cmd - > _next = script - > parse ( in , line + 1 ) ;
cmd - > _next - > log ( false ) ; // suppress logging of subcommand
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
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 . log ( " download terminated " +
QString ( _netsuccess & & _filesuccess ? " successfully " : " with error " ) ) ;
if ( ! _netsuccess ) throw DownloadFailed ( _realfilename ) ;
if ( ! _filesuccess ) throw WriteFileFailed ( _realfilename ) ;
log . plainlog ( " [[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 ) {
_realfilename = reply - > url ( ) . toString ( ) . split ( ' / ' ) . last ( ) ;
if ( _filename . size ( ) )
_realfilename = _filename ;
else if ( reply - > header ( QNetworkRequest : : ContentDispositionHeader )
. isValid ( ) ) {
QString part ( reply - > header ( QNetworkRequest : : ContentDispositionHeader )
. toString ( ) ) ;
if ( part . contains ( QRegExp ( " attachment; *filename= " ) ) ) {
part . replace ( QRegExp ( " .*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
" click <selector> "
" \n \n "
" Click on the specified element " ;
}
QString command ( ) const {
return " click " + _selector ;
}
std : : shared_ptr < Command > parse ( Script * , QString args , QStringList & , int ) {
std : : shared_ptr < Click > cmd ( new Click ( ) ) ;
cmd - > _selector = args ;
return cmd ;
}
bool execute ( Script * script , QWebFrame * frame ) {
Logger log ( this , script ) ;
realMouseClick ( frame , script - > replacevars ( _selector ) ) ;
return true ;
}
private :
QString _selector ;
} ;
class Set : public Command {
public :
QString tag ( ) const {
return " set " ;
}
QString description ( ) const {
return
" set <variable>=<value> \n "
" set <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. " ;
}
QString command ( ) const {
if ( _next )
return " set " + _name + " \n " + _next - > command ( ) ;
else
return " set " + _name + " = " + _value ;
}
std : : shared_ptr < Command > parse ( Script * script , QString args ,
QStringList & in , int line ) {
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 - > parse ( in , line + 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 ( script - > replacevars ( _name ) ,
script - > replacevars ( _next - > result ( ) ) ) ;
} else {
script - > set ( script - > replacevars ( _name ) ,
script - > replacevars ( _value ) ) ;
}
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
" unset <variable> "
" \n \n "
" Undo the setting of a variable. The opposite of «set». " ;
}
QString command ( ) const {
return " unset " + _name ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , 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
" timeout <seconds> "
" \n \n "
" Set the timeout in seconds (defaults to 10). " ;
}
QString command ( ) const {
return " timeout " + _timeout ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , 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 ) throw 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
" ca-certificate <filename.pem> "
" \n \n "
" Load a CA certificate that will be accepted on SSL connections. " ;
}
QString command ( ) const {
return " ca-certificate " + _filename ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , 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 ( filename ) ;
if ( ! cacertfile . exists ( ) | | ! cacertfile . open ( QIODevice : : ReadOnly ) )
throw FileNotFound ( filename ) ;
QSslCertificate cacert ( & cacertfile ) ;
if ( cacert . isNull ( ) ) throw 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
" client-certificate <certfile.pem> <keyfile.key> <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 " client-certificate " + _certfile + " " + _keyfile + " " + _password ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & , 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 ( filename ) ;
if ( ! certfile . exists ( ) | | ! certfile . open ( QIODevice : : ReadOnly ) )
throw FileNotFound ( filename ) ;
QSslCertificate cert ( & certfile ) ;
if ( cert . isNull ( ) ) throw NotACertificate ( filename ) ;
sslConfig . setLocalCertificate ( cert ) ;
filename = script - > replacevars ( _keyfile ) ;
QFile keyfile ( filename ) ;
if ( ! keyfile . exists ( ) | | ! keyfile . open ( QIODevice : : ReadOnly ) )
throw FileNotFound ( filename ) ;
keyfile . open ( QIODevice : : ReadOnly ) ;
QSslKey k ( & keyfile , QSsl : : Rsa , QSsl : : Pem ,
QSsl : : PrivateKey , _password . toUtf8 ( ) ) ;
if ( k . isNull ( ) ) throw KeyNotReadable ( filename ) ;
sslConfig . setPrivateKey ( k ) ;
QSslConfiguration : : setDefaultConfiguration ( sslConfig ) ;
return true ;
}
private :
QString _certfile ;
QString _keyfile ;
QString _password ;
} ;
/* Template:
class : public Command {
public :
QString tag ( ) const {
return " " ;
}
QString description ( ) const {
return
" "
" \n \n "
" " ;
}
QString command ( ) const {
return " " ;
}
std : : shared_ptr < Command > parse ( Script * , QString args ,
QStringList & in , 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 ) {
if ( ! script - > screenshots ( ) ) return true ;
Logger log ( this , script ) ;
QString filename ( screenshot ( line ( ) , targetdir ( ) ,
QFileInfo ( testsuite ( ) ) . baseName ( ) ,
_filename , frame ) ) ;
log . plainlog ( " [[ATTACHMENT| " + filename + " ]] " ) ;
return true ;
}
inline Logger : : Logger ( Command * command , Script * script ) :
_command ( command ) , _script ( script ) {
if ( _command - > log ( ) ) {
_script - > log ( " ---- line: " + QString : : number ( _command - > line ( ) ) ) ;
_script - > log ( _command - > command ( ) ) ;
}
}
inline void Logger : : log ( QString txt ) {
if ( _command - > log ( ) )
_script - > log ( txt ) ;
}
inline void Logger : : plainlog ( QString txt ) {
_script - > plainlog ( txt ) ;
}
inline Logger : : ~ Logger ( ) {
if ( _command - > log ( ) )
_script - > log ( " --------------------- " ) ;
}
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 ) ;
}
# endif