xref: /plugin/combo/vendor/php-webdriver/webdriver/lib/Remote/Service/DriverService.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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