1*04fd306cSNickeau<?php 2*04fd306cSNickeau 3*04fd306cSNickeaunamespace Facebook\WebDriver\Remote\Service; 4*04fd306cSNickeau 5*04fd306cSNickeauuse Exception; 6*04fd306cSNickeauuse Facebook\WebDriver\Net\URLChecker; 7*04fd306cSNickeauuse Symfony\Component\Process\Process; 8*04fd306cSNickeauuse Symfony\Component\Process\ProcessBuilder; 9*04fd306cSNickeau 10*04fd306cSNickeau/** 11*04fd306cSNickeau * Start local WebDriver service (when remote WebDriver server is not used). 12*04fd306cSNickeau * This will start new process of respective browser driver and take care of its lifecycle. 13*04fd306cSNickeau */ 14*04fd306cSNickeauclass DriverService 15*04fd306cSNickeau{ 16*04fd306cSNickeau /** 17*04fd306cSNickeau * @var string 18*04fd306cSNickeau */ 19*04fd306cSNickeau private $executable; 20*04fd306cSNickeau 21*04fd306cSNickeau /** 22*04fd306cSNickeau * @var string 23*04fd306cSNickeau */ 24*04fd306cSNickeau private $url; 25*04fd306cSNickeau 26*04fd306cSNickeau /** 27*04fd306cSNickeau * @var array 28*04fd306cSNickeau */ 29*04fd306cSNickeau private $args; 30*04fd306cSNickeau 31*04fd306cSNickeau /** 32*04fd306cSNickeau * @var array 33*04fd306cSNickeau */ 34*04fd306cSNickeau private $environment; 35*04fd306cSNickeau 36*04fd306cSNickeau /** 37*04fd306cSNickeau * @var Process|null 38*04fd306cSNickeau */ 39*04fd306cSNickeau private $process; 40*04fd306cSNickeau 41*04fd306cSNickeau /** 42*04fd306cSNickeau * @param string $executable 43*04fd306cSNickeau * @param int $port The given port the service should use. 44*04fd306cSNickeau * @param array $args 45*04fd306cSNickeau * @param array|null $environment Use the system environment if it is null 46*04fd306cSNickeau */ 47*04fd306cSNickeau public function __construct($executable, $port, $args = [], $environment = null) 48*04fd306cSNickeau { 49*04fd306cSNickeau $this->setExecutable($executable); 50*04fd306cSNickeau $this->url = sprintf('http://localhost:%d', $port); 51*04fd306cSNickeau $this->args = $args; 52*04fd306cSNickeau $this->environment = $environment ?: $_ENV; 53*04fd306cSNickeau } 54*04fd306cSNickeau 55*04fd306cSNickeau /** 56*04fd306cSNickeau * @return string 57*04fd306cSNickeau */ 58*04fd306cSNickeau public function getURL() 59*04fd306cSNickeau { 60*04fd306cSNickeau return $this->url; 61*04fd306cSNickeau } 62*04fd306cSNickeau 63*04fd306cSNickeau /** 64*04fd306cSNickeau * @return DriverService 65*04fd306cSNickeau */ 66*04fd306cSNickeau public function start() 67*04fd306cSNickeau { 68*04fd306cSNickeau if ($this->process !== null) { 69*04fd306cSNickeau return $this; 70*04fd306cSNickeau } 71*04fd306cSNickeau 72*04fd306cSNickeau $this->process = $this->createProcess(); 73*04fd306cSNickeau $this->process->start(); 74*04fd306cSNickeau 75*04fd306cSNickeau $this->checkWasStarted($this->process); 76*04fd306cSNickeau 77*04fd306cSNickeau $checker = new URLChecker(); 78*04fd306cSNickeau $checker->waitUntilAvailable(20 * 1000, $this->url . '/status'); 79*04fd306cSNickeau 80*04fd306cSNickeau return $this; 81*04fd306cSNickeau } 82*04fd306cSNickeau 83*04fd306cSNickeau /** 84*04fd306cSNickeau * @return DriverService 85*04fd306cSNickeau */ 86*04fd306cSNickeau public function stop() 87*04fd306cSNickeau { 88*04fd306cSNickeau if ($this->process === null) { 89*04fd306cSNickeau return $this; 90*04fd306cSNickeau } 91*04fd306cSNickeau 92*04fd306cSNickeau $this->process->stop(); 93*04fd306cSNickeau $this->process = null; 94*04fd306cSNickeau 95*04fd306cSNickeau $checker = new URLChecker(); 96*04fd306cSNickeau $checker->waitUntilUnavailable(3 * 1000, $this->url . '/shutdown'); 97*04fd306cSNickeau 98*04fd306cSNickeau return $this; 99*04fd306cSNickeau } 100*04fd306cSNickeau 101*04fd306cSNickeau /** 102*04fd306cSNickeau * @return bool 103*04fd306cSNickeau */ 104*04fd306cSNickeau public function isRunning() 105*04fd306cSNickeau { 106*04fd306cSNickeau if ($this->process === null) { 107*04fd306cSNickeau return false; 108*04fd306cSNickeau } 109*04fd306cSNickeau 110*04fd306cSNickeau return $this->process->isRunning(); 111*04fd306cSNickeau } 112*04fd306cSNickeau 113*04fd306cSNickeau /** 114*04fd306cSNickeau * @deprecated Has no effect. Will be removed in next major version. Executable is now checked 115*04fd306cSNickeau * when calling setExecutable(). 116*04fd306cSNickeau * @param string $executable 117*04fd306cSNickeau * @return string 118*04fd306cSNickeau */ 119*04fd306cSNickeau protected static function checkExecutable($executable) 120*04fd306cSNickeau { 121*04fd306cSNickeau return $executable; 122*04fd306cSNickeau } 123*04fd306cSNickeau 124*04fd306cSNickeau /** 125*04fd306cSNickeau * @param string $executable 126*04fd306cSNickeau * @throws Exception 127*04fd306cSNickeau */ 128*04fd306cSNickeau protected function setExecutable($executable) 129*04fd306cSNickeau { 130*04fd306cSNickeau if ($this->isExecutable($executable)) { 131*04fd306cSNickeau $this->executable = $executable; 132*04fd306cSNickeau 133*04fd306cSNickeau return; 134*04fd306cSNickeau } 135*04fd306cSNickeau 136*04fd306cSNickeau throw new Exception( 137*04fd306cSNickeau sprintf( 138*04fd306cSNickeau '"%s" is not executable. Make sure the path is correct or use environment variable to specify' 139*04fd306cSNickeau . ' location of the executable.', 140*04fd306cSNickeau $executable 141*04fd306cSNickeau ) 142*04fd306cSNickeau ); 143*04fd306cSNickeau } 144*04fd306cSNickeau 145*04fd306cSNickeau /** 146*04fd306cSNickeau * @param Process $process 147*04fd306cSNickeau */ 148*04fd306cSNickeau protected function checkWasStarted($process) 149*04fd306cSNickeau { 150*04fd306cSNickeau usleep(10000); // wait 10ms, otherwise the asynchronous process failure may not yet be propagated 151*04fd306cSNickeau 152*04fd306cSNickeau if (!$process->isRunning()) { 153*04fd306cSNickeau throw new Exception( 154*04fd306cSNickeau sprintf( 155*04fd306cSNickeau 'Error starting driver executable "%s": %s', 156*04fd306cSNickeau $process->getCommandLine(), 157*04fd306cSNickeau $process->getErrorOutput() 158*04fd306cSNickeau ) 159*04fd306cSNickeau ); 160*04fd306cSNickeau } 161*04fd306cSNickeau } 162*04fd306cSNickeau 163*04fd306cSNickeau /** 164*04fd306cSNickeau * @return Process 165*04fd306cSNickeau */ 166*04fd306cSNickeau private function createProcess() 167*04fd306cSNickeau { 168*04fd306cSNickeau // BC: ProcessBuilder deprecated since Symfony 3.4 and removed in Symfony 4.0. 169*04fd306cSNickeau if (class_exists(ProcessBuilder::class) 170*04fd306cSNickeau && mb_strpos('@deprecated', (new \ReflectionClass(ProcessBuilder::class))->getDocComment()) === false 171*04fd306cSNickeau ) { 172*04fd306cSNickeau $processBuilder = (new ProcessBuilder()) 173*04fd306cSNickeau ->setPrefix($this->executable) 174*04fd306cSNickeau ->setArguments($this->args) 175*04fd306cSNickeau ->addEnvironmentVariables($this->environment); 176*04fd306cSNickeau 177*04fd306cSNickeau return $processBuilder->getProcess(); 178*04fd306cSNickeau } 179*04fd306cSNickeau // Safe to use since Symfony 3.3 180*04fd306cSNickeau $commandLine = array_merge([$this->executable], $this->args); 181*04fd306cSNickeau 182*04fd306cSNickeau return new Process($commandLine, null, $this->environment); 183*04fd306cSNickeau } 184*04fd306cSNickeau 185*04fd306cSNickeau /** 186*04fd306cSNickeau * Check whether given file is executable directly or using system PATH 187*04fd306cSNickeau * 188*04fd306cSNickeau * @param string $filename 189*04fd306cSNickeau * @return bool 190*04fd306cSNickeau */ 191*04fd306cSNickeau private function isExecutable($filename) 192*04fd306cSNickeau { 193*04fd306cSNickeau if (is_executable($filename)) { 194*04fd306cSNickeau return true; 195*04fd306cSNickeau } 196*04fd306cSNickeau if ($filename !== basename($filename)) { // $filename is an absolute path, do no try to search it in PATH 197*04fd306cSNickeau return false; 198*04fd306cSNickeau } 199*04fd306cSNickeau 200*04fd306cSNickeau $paths = explode(PATH_SEPARATOR, getenv('PATH')); 201*04fd306cSNickeau foreach ($paths as $path) { 202*04fd306cSNickeau if (is_executable($path . DIRECTORY_SEPARATOR . $filename)) { 203*04fd306cSNickeau return true; 204*04fd306cSNickeau } 205*04fd306cSNickeau } 206*04fd306cSNickeau 207*04fd306cSNickeau return false; 208*04fd306cSNickeau } 209*04fd306cSNickeau} 210