@@ -530,6 +530,202 @@ function runTest(what, options, callback) {
530530 return api . call ( this , paths . test , callback , query , options ) ;
531531}
532532
533+ function runTestAndWait ( what , options , callback ) {
534+ delete options . pollResults ;
535+ delete options . timeout ;
536+ options = Object . assign ( options , { pollResults : 10 } ) ;
537+
538+ var query = { } ;
539+
540+ callback = callback || options ;
541+ options = options === callback ? { } : helper . deepClone ( options ) ;
542+
543+ // testing url or script?
544+ query [ reSpace . test ( what ) ? "script" : "url" ] = what ;
545+ // set dummy url when scripting, needed when webdriver script
546+ if ( query . script ) {
547+ query . url = "https://www.webpagetest.org" ;
548+ }
549+ helper . setQuery ( mapping . commands . test , options , query ) ;
550+
551+ // connectivity
552+ if ( reConnectivity . test ( options . connectivity ) && query . location ) {
553+ query . location += "." + options . connectivity ;
554+ }
555+
556+ // json output format
557+ query . f = "json" ;
558+
559+ // API key
560+ if ( ! query . k && this . config . key ) {
561+ query . k = this . config . key ;
562+ }
563+
564+ // synchronous tests with results
565+ var testId ,
566+ polling ,
567+ server ,
568+ listen ,
569+ timerout ,
570+ resultsOptions = { } ;
571+
572+ function resultsCallback ( err , data ) {
573+ clearTimeout ( timerout ) ;
574+ if ( options . exitOnResults ) {
575+ process . exit ( err ) ;
576+ } else {
577+ callback ( err , data ) ;
578+ }
579+ }
580+
581+ function poll ( err , data ) {
582+ // poll again when test started but not complete
583+ // and not when specs are done testing
584+ if (
585+ ! err &&
586+ ( ! data || ( data && data . data && data . statusCode !== 200 ) ) &&
587+ ! ( typeof err === "number" && data === undefined )
588+ ) {
589+ console . log (
590+ data && data . data && data . data . statusText
591+ ? data . data . statusText
592+ : "Testing in progress"
593+ ) ;
594+ polling = setTimeout (
595+ getTestResults . bind ( this , testId , resultsOptions , poll . bind ( this ) ) ,
596+ options . pollResults
597+ ) ;
598+ } else {
599+ if ( ! data ) {
600+ data = { testId : testId } ;
601+ }
602+ resultsCallback ( err , data ) ;
603+ }
604+ }
605+
606+ function testCallback ( cb , err , data ) {
607+ if ( err || ! ( data && data . data && data . data . testId ) ) {
608+ return callback ( err || data ) ;
609+ }
610+
611+ testId = data . data . testId ;
612+
613+ if ( options . timeout ) {
614+ timerout = setTimeout ( timeout , options . timeout ) ;
615+ }
616+
617+ if ( cb ) {
618+ cb . call ( this ) ;
619+ }
620+ }
621+
622+ function timeout ( ) {
623+ if ( server ) {
624+ server . close ( ) ;
625+ }
626+ clearTimeout ( polling ) ;
627+ callback ( {
628+ error : {
629+ code : "TIMEOUT" ,
630+ testId : testId ,
631+ message : "timeout" ,
632+ } ,
633+ } ) ;
634+ }
635+
636+ function listener ( ) {
637+ query . pingback = url . format ( {
638+ protocol : "http" ,
639+ hostname : options . waitResults . hostname ,
640+ port : options . waitResults . port ,
641+ pathname : "/testdone" ,
642+ } ) ;
643+
644+ api . call ( this , paths . test , testCallback . bind ( this , null ) , query , options ) ;
645+ }
646+
647+ function wait ( ) {
648+ server . listen ( options . waitResults . port , listen ) ;
649+ return options . waitResults ;
650+ }
651+
652+ // poll|wait results timeout
653+ if ( options . timeout ) {
654+ options . timeout = ( parseInt ( options . timeout , 10 ) || 0 ) * 1000 ;
655+ }
656+
657+ // poll|wait results options
658+ Object . keys ( mapping . options . results ) . forEach ( function resultsOpts ( key ) {
659+ var name = mapping . options . results [ key ] . name ,
660+ value = options [ name ] || options [ key ] ;
661+
662+ if ( value !== undefined ) {
663+ resultsOptions [ name ] = value ;
664+ }
665+ } ) ;
666+
667+ // poll results
668+ if ( options . pollResults && ! options . dryRun ) {
669+ options . pollResults = parseInt ( options . pollResults * 1000 , 10 ) || 5000 ;
670+
671+ return api . call (
672+ this ,
673+ paths . test ,
674+ testCallback . bind ( this , poll ) ,
675+ query ,
676+ options
677+ ) ;
678+ }
679+
680+ // wait results
681+ if ( options . waitResults && ! options . dryRun ) {
682+ options . waitResults = helper . localhost (
683+ options . waitResults ,
684+ WebPageTest . defaultWaitResultsPort
685+ ) ;
686+
687+ listen = listener . bind ( this ) ;
688+
689+ server = http . createServer (
690+ function ( req , res ) {
691+ var uri = url . parse ( req . url , true ) ;
692+
693+ res . statusCode = 204 ;
694+ res . end ( ) ;
695+
696+ if ( uri . pathname === "/testdone" && uri . query . id === testId ) {
697+ server . close (
698+ getTestResults . bind (
699+ this ,
700+ uri . query . id ,
701+ resultsOptions ,
702+ resultsCallback
703+ )
704+ ) ;
705+ }
706+ } . bind ( this )
707+ ) ;
708+
709+ server . on (
710+ "error" ,
711+ function ( err ) {
712+ if ( [ "EACCES" , "EADDRINUSE" ] . indexOf ( err . code ) > - 1 ) {
713+ // remove old unused listener and bump port for next attempt
714+ server . removeListener ( "listening" , listen ) ;
715+ options . waitResults . port ++ ;
716+ wait . call ( this ) ;
717+ } else {
718+ callback ( err ) ;
719+ }
720+ } . bind ( this )
721+ ) ;
722+
723+ return wait . call ( this ) ;
724+ }
725+
726+ return api . call ( this , paths . test , callback , query , options ) ;
727+ }
728+
533729function restartTest ( id , options , callback ) {
534730 var query = { resubmit : id } ;
535731
@@ -932,6 +1128,7 @@ WebPageTest.prototype = {
9321128 getLocations : getLocations ,
9331129 getTesters : getTesters ,
9341130 runTest : runTest ,
1131+ runTestAndWait : runTestAndWait ,
9351132 restartTest : restartTest ,
9361133 cancelTest : cancelTest ,
9371134 getPageSpeedData : getPageSpeedData ,
0 commit comments