├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── selenium-setup.php ├── cacert.pem ├── composer.json ├── config └── default.json ├── lib ├── Binary │ ├── Binary.php │ └── BinaryInterface.php ├── Config │ ├── Config.php │ ├── ConfigFactory.php │ └── ConfigInterface.php ├── Controller │ ├── ListServers.php │ ├── RegisterServer.php │ ├── StartServer.php │ └── StopServer.php ├── Environment.php ├── FileSystem.php ├── FileSystemInterface.php ├── Locker │ ├── Locker.php │ ├── ServerItem.php │ └── ServerItemFactory.php ├── SeleniumSetup.php └── Service │ ├── AbstractService.php │ ├── ListServersService.php │ ├── RegisterServerService.php │ ├── StartServerService.php │ └── StopServerService.php └── selenium-setup /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.phar 3 | vendor/ 4 | /build/logs 5 | /build 6 | /composer.lock 7 | selenium-servers.lock 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | os: 4 | - linux 5 | 6 | php: 7 | - "5.4" 8 | 9 | before_install: 10 | # shutdown servies on Travis, which may have a memory impact 11 | # show memory usage before and after shutdown of services 12 | - sudo service --status-all 13 | - sudo free -m -t 14 | - sudo /etc/init.d/mysql stop 15 | - sudo /etc/init.d/postgresql stop 16 | - sudo /etc/init.d/couchdb stop 17 | - sudo /etc/init.d/redis-server stop 18 | - sudo free -m -t 19 | 20 | before_script: 21 | #- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - 22 | #- sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' 23 | #- sudo apt-get update 24 | #- sudo apt-get install google-chrome-stable 25 | #- composer self-update 26 | - "export DISPLAY=:99.0" 27 | - "sh -e /etc/init.d/xvfb start" 28 | - sleep 6 # give xvfb some time to start 29 | - composer install 30 | 31 | script: 32 | #- google-chrome --version 33 | - firefox --version 34 | - php selenium-setup register secondInstance 4445 35 | 36 | - php selenium-setup start 37 | - php selenium-setup start secondInstance 38 | - sleep 10 # give the instances time to start 39 | 40 | - cat selenium-servers.lock 41 | - export no_proxy="localhost,127.0.0.*" 42 | - wget -v http://localhost:4444/wd/hub/ 43 | # - wget -v http://localhost:4445/wd/hub/ 44 | 45 | # run check 46 | - mkdir tests; cd tests 47 | - wget https://raw.githubusercontent.com/bogdananton/Selenium-Setup-VM/master/tests/composer.json 48 | - wget https://raw.githubusercontent.com/bogdananton/Selenium-Setup-VM/master/tests/test.php 49 | - composer update 50 | # run tests on (default) 4444 port 51 | - phpunit test.php 52 | # run tests on second instance 4445 port 53 | - sed -i -- 's/4444/4445/g' test.php 54 | - phpunit test.php 55 | - cd .. 56 | 57 | after_script: 58 | - cat build/logs/selenium.log 59 | - php selenium-setup servers 60 | 61 | notifications: 62 | email: false 63 | irc: 64 | channels: 65 | - "asimov.freenode.net#mobiledetect" 66 | template: 67 | - "%{repository} (%{commit}): %{message}. Build details: %{build_url}" 68 | - "%{author} said %{commit_subject}. See %{compare_url}" 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Bogdan Anton and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Developer’s Certificate of Origin 1.1 25 | 26 | By making a contribution to this project, I certify that: 27 | 28 | (a) The contribution was created in whole or in part by me and I 29 | have the right to submit it under the open source license 30 | indicated in the file; or 31 | 32 | (b) The contribution is based upon previous work that, to the best 33 | of my knowledge, is covered under an appropriate open source 34 | license and I have the right under that license to submit that 35 | work with modifications, whether created in whole or in part 36 | by me, under the same open source license (unless I am 37 | permitted to submit under a different license), as indicated 38 | in the file; or 39 | 40 | (c) The contribution was provided directly to me by some other 41 | person who certified (a), (b) or (c) and I have not modified 42 | it. 43 | 44 | (d) I understand and agree that this project and the contribution 45 | are public and that a record of the contribution (including all 46 | personal information I submit with it, including my sign-off) is 47 | maintained indefinitely and may be redistributed consistent with 48 | this project or the open source license(s) involved. 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | ____ ___ 3 | /\ _`\ /\_ \ __ 4 | \ \,\L\_\ __\//\ \ __ ___ /\_\ __ __ ___ ___ 5 | \/_\__ \ /'__`\\ \ \ /'__`\/' _ `\/\ \/\ \/\ \ /' __` __`\ 6 | /\ \L\ \/\ __/ \_\ \_/\ __//\ \/\ \ \ \ \ \_\ \/\ \/\ \/\ \ 7 | \ `\____\ \____\/\____\ \____\ \_\ \_\ \_\ \____/\ \_\ \_\ \_\ 8 | \/_____/\/____/\/____/\/____/\/_/\/_/\/_/\/___/ \/_/\/_/\/_/ 9 | Selenium Environment on Windows, Linux and Mac 10 | ``` 11 | 12 | [![Build Status](https://travis-ci.org/bogdananton/Selenium-Setup.svg)](https://travis-ci.org/bogdananton/Selenium-Setup) 13 | ![Build Status](https://img.shields.io/packagist/v/bogdananton/selenium-setup.svg) 14 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/bogdananton/Selenium-Setup/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/bogdananton/Selenium-Setup/?branch=master) 15 | ![PHP 5.4](https://img.shields.io/badge/PHP-5.4-brightgreen.svg) 16 | 17 | ## Install 18 | 19 | 1. `git clone https://github.com/bogdananton/Selenium-Setup.git` 20 | 1. `cd Selenium-Setup` 21 | 1. Download [composer.phar](https://getcomposer.org/composer.phar) 22 | 1. Run `php composer.phar install` 23 | 24 | ## Listing commands 25 | 26 | 1. `php bin/selenium-setup.php list` 27 | 28 | ``` 29 | Selenium Setup version 4.0.0 30 | 31 | Usage: 32 | command [options] [arguments] 33 | 34 | Available commands: 35 | help Displays help for a command 36 | list Lists commands 37 | register Register a SeleniumSetup server instance. 38 | servers List registered Selenium Servers. 39 | start Start Selenium Server setup with all supported drivers attached to it. 40 | stop Stop Selenium Server. 41 | ``` 42 | 43 | ## Running (default instance) 44 | 45 | 1. `php bin/selenium-setup.php start` 46 | 47 | ``` 48 | Usage: 49 | start [options] [--] [] 50 | 51 | Arguments: 52 | name The instance name. [default: "defaultServer"] 53 | ``` 54 | 55 | ## Registering instances 56 | 57 | 1. `php bin/selenium-setup.php register secondInstance 4445` 58 | 59 | ``` 60 | Usage: 61 | register [options] [--] 62 | 63 | Arguments: 64 | name Instance name. 65 | port Instance port. 66 | ``` 67 | 68 | ## Stopping an instance 69 | 70 | 1. `php bin/selenium-setup.php stop secondInstance` 71 | 72 | ``` 73 | Usage: 74 | stop [] 75 | 76 | Arguments: 77 | name The name of the server. [default: "defaultServer"] 78 | ``` 79 | 80 | ## System Requirements 81 | 82 | * Java JRE/JDK (1.6+) 83 | * PHP (5.3+) with curl and open_ssl 84 | * Browser: Chrome, Firefox, IE (only on Windows) 85 | 86 | ## Environment components: 87 | 88 | * [Composer](https://getcomposer.org/) 89 | * [Selenium](http://www.seleniumhq.org) Standalone server 90 | * [Facebook PHP WebDriver](https://github.com/facebook/php-webdriver) 91 | * WebDrivers 92 | * [ChromeDriver](https://code.google.com/p/selenium/wiki/ChromeDriver) 93 | * [FirefoxDriver](https://code.google.com/p/selenium/wiki/FirefoxDriver) 94 | * [IEDriver](https://code.google.com/p/selenium/wiki/InternetExplorerDriver) 95 | * You need to have Chrome, Firefox or IE installed. 96 | 97 | ## Demo 98 | 99 | [![asciicast](https://asciinema.org/a/5s4dt4szujci9dfcx2fe9qwt4.png)](https://asciinema.org/a/5s4dt4szujci9dfcx2fe9qwt4) 100 | -------------------------------------------------------------------------------- /bin/selenium-setup.php: -------------------------------------------------------------------------------- 1 | $value) { 18 | if (property_exists($binary, $key)) { 19 | $binary->{$key} = $value; 20 | } 21 | } 22 | 23 | return $binary; 24 | } 25 | 26 | /** 27 | * @return mixed 28 | */ 29 | public function getLabel() 30 | { 31 | return $this->label; 32 | } 33 | 34 | /** 35 | * @param mixed $label 36 | * @return Binary 37 | */ 38 | public function setLabel($label) 39 | { 40 | $this->label = $label; 41 | return $this; 42 | } 43 | 44 | /** 45 | * @return mixed 46 | */ 47 | public function getVersion() 48 | { 49 | return $this->version; 50 | } 51 | 52 | /** 53 | * @param mixed $version 54 | * @return Binary 55 | */ 56 | public function setVersion($version) 57 | { 58 | $this->version = $version; 59 | return $this; 60 | } 61 | 62 | /** 63 | * @return mixed 64 | */ 65 | public function getDownloadUrl() 66 | { 67 | return $this->downloadUrl; 68 | } 69 | 70 | /** 71 | * @param mixed $downloadUrl 72 | * @return Binary 73 | */ 74 | public function setDownloadUrl($downloadUrl) 75 | { 76 | $this->downloadUrl = $downloadUrl; 77 | return $this; 78 | } 79 | 80 | /** 81 | * @return mixed 82 | */ 83 | public function getBinName() 84 | { 85 | return $this->binName; 86 | } 87 | 88 | /** 89 | * @param mixed $binName 90 | * @return Binary 91 | */ 92 | public function setBinName($binName) 93 | { 94 | $this->binName = $binName; 95 | return $this; 96 | } 97 | 98 | /** 99 | * @return null 100 | */ 101 | public function getOs() 102 | { 103 | return $this->os; 104 | } 105 | 106 | /** 107 | * @param null $os 108 | * @return Binary 109 | */ 110 | public function setOs($os) 111 | { 112 | $this->os = $os; 113 | return $this; 114 | } 115 | 116 | /** 117 | * @return null 118 | */ 119 | public function getOsType() 120 | { 121 | return $this->osType; 122 | } 123 | 124 | /** 125 | * @param null $osType 126 | * @return Binary 127 | */ 128 | public function setOsType($osType) 129 | { 130 | $this->osType = $osType; 131 | return $this; 132 | } 133 | 134 | public function toArray() 135 | { 136 | return (array)get_object_vars($this); 137 | } 138 | } -------------------------------------------------------------------------------- /lib/Binary/BinaryInterface.php: -------------------------------------------------------------------------------- 1 | toArray(); 29 | 30 | /** 31 | * @var integer $index 32 | * @var Binary $binary 33 | */ 34 | foreach ($this->getBinaries() as $index => $binary) { 35 | $result['binaries'][$index] = $binary->toArray(); 36 | } 37 | 38 | return json_encode($result, JSON_PRETTY_PRINT); 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public static function getAllProperties() 45 | { 46 | return array_keys(get_object_vars(new self)); 47 | } 48 | 49 | public function setFilePath($filePath) 50 | { 51 | $this->filePath = $filePath; 52 | return $this; 53 | } 54 | 55 | public function getFilePath() 56 | { 57 | return $this->filePath; 58 | } 59 | 60 | /** 61 | * @return mixed 62 | */ 63 | public function getName() 64 | { 65 | return $this->name; 66 | } 67 | 68 | /** 69 | * @param mixed $name 70 | * @return Config 71 | */ 72 | public function setName($name) 73 | { 74 | $this->name = $name; 75 | return $this; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getHostname() 82 | { 83 | return $this->hostname; 84 | } 85 | 86 | /** 87 | * @param string $hostname 88 | * @return Config 89 | */ 90 | public function setHostname($hostname) 91 | { 92 | $this->hostname = $hostname; 93 | return $this; 94 | } 95 | 96 | /** 97 | * @return int 98 | */ 99 | public function getPort() 100 | { 101 | return $this->port; 102 | } 103 | 104 | /** 105 | * @param int $port 106 | * @return Config 107 | */ 108 | public function setPort($port) 109 | { 110 | $this->port = $port; 111 | return $this; 112 | } 113 | 114 | /** 115 | * @return string 116 | */ 117 | public function getProxyHost() 118 | { 119 | return $this->proxyHost; 120 | } 121 | 122 | /** 123 | * @param string $proxyHost 124 | * @return Config 125 | */ 126 | public function setProxyHost($proxyHost) 127 | { 128 | $this->proxyHost = $proxyHost; 129 | return $this; 130 | } 131 | 132 | /** 133 | * @return int 134 | */ 135 | public function getProxyPort() 136 | { 137 | return $this->proxyPort; 138 | } 139 | 140 | /** 141 | * @param int $proxyPort 142 | * @return Config 143 | */ 144 | public function setProxyPort($proxyPort) 145 | { 146 | $this->proxyPort = $proxyPort; 147 | return $this; 148 | } 149 | 150 | /** 151 | * @return string 152 | */ 153 | public function getBuildPath() 154 | { 155 | return $this->buildPath; 156 | } 157 | 158 | /** 159 | * @param string $buildPath 160 | * @return Config 161 | */ 162 | public function setBuildPath($buildPath) 163 | { 164 | $this->buildPath = $buildPath; 165 | return $this; 166 | } 167 | 168 | /** 169 | * @return mixed 170 | */ 171 | public function getTmpPath() 172 | { 173 | return $this->tmpPath; 174 | } 175 | 176 | /** 177 | * @param mixed $tmpPath 178 | * @return Config 179 | */ 180 | public function setTmpPath($tmpPath) 181 | { 182 | $this->tmpPath = $tmpPath; 183 | return $this; 184 | } 185 | 186 | /** 187 | * @return string 188 | */ 189 | public function getLogsPath() 190 | { 191 | return $this->logsPath; 192 | } 193 | 194 | /** 195 | * @param string $logsPath 196 | * @return Config 197 | */ 198 | public function setLogsPath($logsPath) 199 | { 200 | $this->logsPath = $logsPath; 201 | return $this; 202 | } 203 | 204 | public function setBinaries(array $binaries) 205 | { 206 | $this->binaries = $binaries; 207 | } 208 | 209 | public function getBinaries() 210 | { 211 | return $this->binaries; 212 | } 213 | 214 | public function setBinary($binaryId, Binary $binaryInfo) 215 | { 216 | $this->binaries[$binaryId] = $binaryInfo; 217 | } 218 | 219 | public function getBinary($binaryName) 220 | { 221 | return isset($this->binaries[$binaryName]) ? $this->binaries[$binaryName] : null; 222 | } 223 | } -------------------------------------------------------------------------------- /lib/Config/ConfigFactory.php: -------------------------------------------------------------------------------- 1 | readFile($configFilePath); 19 | $configObj = json_decode($configContents); 20 | 21 | // @todo: Validate config. 22 | 23 | $config = new Config(); 24 | 25 | // Normalize the paths. 26 | $buildPath = str_replace('{$APP_ROOT_PATH}', SeleniumSetup::$APP_ROOT_PATH, $configObj->buildPath); 27 | $tmpPath = str_replace('{$APP_ROOT_PATH}', SeleniumSetup::$APP_ROOT_PATH, $configObj->tmpPath); 28 | $logsPath = str_replace('{$APP_ROOT_PATH}', SeleniumSetup::$APP_ROOT_PATH, $configObj->logsPath); 29 | 30 | $config 31 | ->setName($configObj->name) 32 | ->setHostname($configObj->hostname) 33 | ->setPort($configObj->port) 34 | ->setProxyHost($configObj->proxyHost) 35 | ->setProxyPort($configObj->proxyPort) 36 | // Set absolute paths (needed for issuing CLI commands). 37 | ->setBuildPath($buildPath) 38 | ->setTmpPath($tmpPath) 39 | ->setLogsPath($logsPath) 40 | ->setFilePath($configFilePath); 41 | 42 | foreach ($configObj->binaries as $binaryId => $binaryInfo) { 43 | $binary = Binary::createFromObject($binaryInfo); 44 | $config->setBinary($binaryId, $binary); 45 | } 46 | 47 | return $config; 48 | } 49 | } -------------------------------------------------------------------------------- /lib/Config/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | setName('servers') 24 | ->setDescription('List registered Selenium Servers.'); 25 | } 26 | 27 | /** 28 | * Execute the command. 29 | * @todo Decide if this can be isolated into a service. 30 | * 31 | * @param \Symfony\Component\Console\Input\InputInterface $input 32 | * @param \Symfony\Component\Console\Output\OutputInterface $output 33 | * @return void 34 | */ 35 | public function execute(InputInterface $input, OutputInterface $output) 36 | { 37 | // Prepare. 38 | $locker = new Locker(); 39 | $locker->openLockFile(); 40 | 41 | // View. 42 | $table = $this->getHelper('table'); 43 | $table 44 | ->setHeaders(ServerItem::getAllProperties()) 45 | ->setRows($locker->toArray()); 46 | $table->render($output); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /lib/Controller/RegisterServer.php: -------------------------------------------------------------------------------- 1 | setName(self::CLI_COMMAND) 26 | ->setDescription('Register a SeleniumSetup server instance.') 27 | ->addOption('config', 'c', InputOption::VALUE_OPTIONAL, 'The config path.') 28 | ->addArgument('name', InputArgument::REQUIRED, 'Instance name.') 29 | ->addArgument('port', InputArgument::REQUIRED, 'Instance port.'); 30 | } 31 | 32 | /** 33 | * Execute the command. 34 | * 35 | * @param \Symfony\Component\Console\Input\InputInterface $input 36 | * @param \Symfony\Component\Console\Output\OutputInterface $output 37 | * @return void 38 | */ 39 | protected function execute(InputInterface $input, OutputInterface $output) 40 | { 41 | $configFilePath = null; 42 | 43 | if ($input->getOption('config')) { 44 | $configFilePath = realpath($input->getOption('config')); 45 | } 46 | 47 | // Prepare. 48 | $config = ConfigFactory::createFromConfigFile($configFilePath); 49 | $env = new Environment($config, $input, $output); 50 | 51 | $handler = new RegisterServerService($config, $env, $input, $output); 52 | $handler->handle(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/Controller/StartServer.php: -------------------------------------------------------------------------------- 1 | setName(self::CLI_COMMAND) 30 | ->setDescription('Start Selenium Server setup with all supported drivers attached to it.') 31 | ->addArgument('name', InputArgument::OPTIONAL, 'The instance name.', $defaultName) 32 | ->addOption('config', 'c', InputOption::VALUE_OPTIONAL, 'The config path.'); 33 | } 34 | 35 | /** 36 | * Execute the command. 37 | * 38 | * @param \Symfony\Component\Console\Input\InputInterface $input 39 | * @param \Symfony\Component\Console\Output\OutputInterface $output 40 | * @return void 41 | */ 42 | protected function execute(InputInterface $input, OutputInterface $output) 43 | { 44 | $configFilePath = null; 45 | 46 | if ($input->getArgument('name')) { 47 | $configFilePath = SeleniumSetup::$APP_CONF_PATH . DIRECTORY_SEPARATOR . $input->getArgument('name') . '.json'; 48 | } 49 | 50 | if ($input->getOption('config')) { 51 | $configFilePath = realpath($input->getOption('config')); 52 | } 53 | 54 | // Prepare. 55 | $config = ConfigFactory::createFromConfigFile($configFilePath); 56 | $env = new Environment($config, $input, $output); 57 | 58 | $handler = new StartServerService($config, $env, $input, $output); 59 | 60 | if ($handler->test()) { 61 | if (!$env->isAdmin()) { 62 | $output->writeln('Running without elevated rights.'); 63 | } 64 | $output->writeln('Let\'s go ...'); 65 | $handler->handle(); 66 | } else { 67 | $output->writeln('Missing required components. Please review your setup.'); 68 | } 69 | 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /lib/Controller/StopServer.php: -------------------------------------------------------------------------------- 1 | setName(self::CLI_COMMAND) 29 | ->setDescription('Stop Selenium Server.') 30 | ->addArgument('name', InputArgument::OPTIONAL, 'The name of the server.', $defaultName); 31 | } 32 | 33 | /** 34 | * Execute the command. 35 | * 36 | * @param \Symfony\Component\Console\Input\InputInterface $input 37 | * @param \Symfony\Component\Console\Output\OutputInterface $output 38 | * @return void 39 | */ 40 | public function execute(InputInterface $input, OutputInterface $output) 41 | { 42 | $locker = new Locker(); 43 | $locker->openLockFile(); 44 | $serverItem = $locker->getServer($input->getArgument('name')); 45 | 46 | // Prepare. 47 | $config = ConfigFactory::createFromConfigFile($serverItem->getConfigFilePath()); 48 | $env = new Environment($config, $input, $output); 49 | 50 | $handler = new StopServerService($config, $env, $input, $output); 51 | $handler->handle(); 52 | } 53 | } -------------------------------------------------------------------------------- /lib/Environment.php: -------------------------------------------------------------------------------- 1 | config = $config; 33 | $this->fileSystem = new FileSystem(); 34 | $this->input = $input; 35 | $this->output = $output; 36 | } 37 | 38 | // @todo Move to public methods into SeleniumSetup\Environment. 39 | public function test() 40 | { 41 | // Pre-requisites. 42 | $canInstall = true; 43 | $writeln = []; 44 | 45 | // Start checking. 46 | $javaVersion = $this->getJavaVersion(); 47 | 48 | if (empty($javaVersion)) { 49 | $writeln[] = '[ ] Java is not installed.'; 50 | $canInstall = false; 51 | } else { 52 | $writeln[] = '[x] Java is installed.'; 53 | if ($this->isJavaVersionDeprecated($javaVersion)) { 54 | $writeln[] = '[ ] Your Java version needs to be >= 1.6'; 55 | $canInstall = false; 56 | } else { 57 | $writeln[] = '[x] Your Java version ' . $javaVersion . ' seems up to date.'; 58 | } 59 | } 60 | 61 | if ($this->isPHPVersionDeprecated()) { 62 | $writeln[] = '[ ] Your PHP version ' . $this->getPHPVersion() . ' should be >= 5.3'; 63 | $canInstall = false; 64 | } else { 65 | $writeln[] = '[x] Your PHP version is ' . $this->getPHPVersion() . ''; 66 | } 67 | 68 | if (!$this->hasPHPCurlExtInstalled()) { 69 | $writeln[] = '[ ] cURL extension for PHP is missing.'; 70 | $canInstall = false; 71 | } else { 72 | $writeln[] = '[x] cURL ' . $this->getPHPCurlExtVersion() . ' extension is installed.'; 73 | } 74 | 75 | if (!$this->hasPHPOpenSSLExtInstalled()) { 76 | $writeln[] = '[ ] OpenSSL extension for PHP is missing.'; 77 | $canInstall = false; 78 | } else { 79 | $writeln[] = '[x] ' . $this->getPHPOpenSSLExtVersion() . ' extension is installed.'; 80 | } 81 | 82 | $this->output->writeln($writeln); 83 | 84 | return $canInstall; 85 | } 86 | 87 | // @todo Fine-tune the Windows and Mac detection if possible. 88 | public function getOsName() 89 | { 90 | if (strtolower(substr(PHP_OS, 0, 3)) === 'win') { 91 | return self::OS_WINDOWS; 92 | } else if ( 93 | strpos(strtolower(PHP_OS), 'mac') !== false || 94 | strpos(strtolower(PHP_OS), 'darwin') !== false 95 | ) { 96 | return self::OS_MAC; 97 | } else { 98 | // Assume Linux. 99 | return self::OS_LINUX; 100 | } 101 | } 102 | 103 | public function getOsVersion() 104 | { 105 | // TODO: Implement getOsVersion() method. 106 | } 107 | 108 | public function getOsType() 109 | { 110 | //$type = php_uname('m'); 111 | if (strlen(decbin(~0)) == 64) { 112 | return self::OS_TYPE_64BIT; 113 | } else { 114 | return self::OS_TYPE_32BIT; 115 | } 116 | } 117 | 118 | public function isWindows() 119 | { 120 | return $this->getOsName() == self::OS_WINDOWS; 121 | } 122 | 123 | public function isMac() 124 | { 125 | return $this->getOsName() == self::OS_MAC; 126 | } 127 | 128 | public function isLinux() 129 | { 130 | return $this->getOsName() == self::OS_LINUX; 131 | } 132 | 133 | public function isAdmin() 134 | { 135 | if ($this->isWindows()) { 136 | $cmd = 'NET SESSION'; 137 | $lookForNegative = '^System error'; 138 | 139 | } else { 140 | $cmd = 'sudo -n true'; 141 | $lookForNegative = '^sudo\: a password is required'; 142 | } 143 | 144 | $output = new BufferedOutput(); 145 | 146 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 147 | $process->run(function($type, $line) use ($output) { 148 | $output->write($line); 149 | }); 150 | 151 | return !(preg_match('/' . $lookForNegative . '/is', $output->fetch())); 152 | } 153 | 154 | public function getJavaVersion() 155 | { 156 | $cmd = 'java -version'; 157 | 158 | $output = new BufferedOutput(); 159 | 160 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 161 | $process->run(function($type, $line) use ($output) { 162 | $output->write($line); 163 | }); 164 | 165 | preg_match('/version "([0-9._]+)"/', $output->fetch(), $javaVersionMatches); 166 | $javaVersion = isset($javaVersionMatches[1]) ? $javaVersionMatches[1] : null; 167 | 168 | return $javaVersion; 169 | } 170 | 171 | public function isJavaVersionDeprecated($javaVersion) 172 | { 173 | return version_compare($javaVersion, '1.6') < 0; 174 | } 175 | 176 | public function hasJavaCli() 177 | { 178 | // TODO: Implement hasJavaCli() method. 179 | } 180 | 181 | public function hasPHPInstalled() 182 | { 183 | // TODO: Implement hasPHPInstalled() method. 184 | } 185 | 186 | public function getPHPVersion() 187 | { 188 | return PHP_VERSION; 189 | } 190 | 191 | public function isPHPVersionDeprecated() 192 | { 193 | return version_compare($this->getPHPVersion(), '5.3') < 0; 194 | } 195 | 196 | public function canUseTheLatestPHPUnitVersion() 197 | { 198 | return version_compare($this->getPHPVersion(), '5.6') >= 0; 199 | } 200 | 201 | public function hasPHPCurlExtInstalled() 202 | { 203 | return function_exists('curl_version'); 204 | } 205 | 206 | public function getPHPCurlExtVersion() 207 | { 208 | return curl_version()['version']; 209 | } 210 | 211 | public function hasPHPOpenSSLExtInstalled() 212 | { 213 | return extension_loaded('openssl'); 214 | } 215 | 216 | public function getPHPOpenSSLExtVersion() 217 | { 218 | return OPENSSL_VERSION_TEXT; 219 | } 220 | 221 | // @todo Decide if this is still neded. 222 | public function hasCurlCli() 223 | { 224 | //$command = new GetCurlVersionCommand(); 225 | //$commandInput = new ArrayInput([]); 226 | //$commandOutput = new BufferedOutput(); 227 | //$returnCode = $command->run($commandInput, $commandOutput); 228 | 229 | //return preg_match('/^curl ([0-9._]+)/', $commandOutput->fetch()); 230 | } 231 | 232 | public function getEnvVar($varName) 233 | { 234 | return getenv($varName); 235 | } 236 | 237 | public function setEnvVar($varName, $varValue = '') 238 | { 239 | if ($varValue != '') { 240 | putenv($varName . '=' . $varValue); 241 | } else { 242 | putenv($varName); 243 | } 244 | } 245 | 246 | public function addPathToGlobalPath($path) 247 | { 248 | if ($this->isWindows()) { 249 | $separator = ';'; 250 | } else { 251 | $separator = ':'; 252 | } 253 | 254 | putenv('PATH=' . getenv('PATH') . $separator . $path); 255 | $this->output->writeln(sprintf('Added %s to global path.', $path)); 256 | } 257 | 258 | public function download($from, $to) 259 | { 260 | $client = new Client(); 261 | $client->setDefaultOption('verify', SeleniumSetup::$APP_ROOT_PATH . DIRECTORY_SEPARATOR . SeleniumSetup::SSL_CERT_FILENAME); 262 | $request = $client->createRequest('GET', $from, ['save_to'=> $to]); 263 | 264 | $computeRemainingSize = function(ProgressEvent $e) { 265 | if ($e->downloaded <= 0) { 266 | return 0; 267 | } 268 | $remainingSize = $e->downloadSize - $e->downloaded; 269 | if ($remainingSize > 0) { 270 | return round($e->downloaded / $e->downloadSize, 2) * 100; 271 | } else { 272 | return 100; 273 | } 274 | }; 275 | 276 | // $progress = new ProgressBar($output, 5); 277 | // $progress->start(); 278 | $output = new BufferedOutput(); 279 | 280 | $request->getEmitter()->on('progress', function(ProgressEvent $e) use ($computeRemainingSize, $output) { 281 | 282 | $output->write( 283 | sprintf("Downloaded %s%%\r", $computeRemainingSize($e)) 284 | ); 285 | 286 | //$a = $computeRemainingSize($e); 287 | //if ($a == 100) { 288 | // $progress->finish(); 289 | //} else { 290 | // if ($a % 10 == 0) { 291 | // $progress->advance(); 292 | // } 293 | //} 294 | }); 295 | 296 | $client->send($request); 297 | 298 | return $output->fetch(); 299 | } 300 | 301 | public function getCurlVersion() 302 | { 303 | $cmd = 'curl -V'; 304 | 305 | $output = $this->output; 306 | 307 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 308 | $process->run(function($type, $line) use ($output) { 309 | $output->write($line); 310 | }); 311 | } 312 | 313 | public function killProcessByPid($pid) 314 | { 315 | if ($this->isWindows()) { 316 | $cmd = 'taskkill /F /PID %d'; 317 | } else { 318 | $cmd = 'kill -9 %d'; 319 | } 320 | 321 | $cmd = sprintf($cmd, $pid); 322 | 323 | $output = $this->output; 324 | 325 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 326 | $process->run(function($type, $line) use ($output) { 327 | $output->write($line); 328 | }); 329 | } 330 | 331 | public function killProcessByName($processName) 332 | { 333 | if ($this->isWindows()) { 334 | $cmd = 'taskkill /F /IM %s'; 335 | } else { 336 | $cmd = 'pgrep -f "%s" | xargs kill'; 337 | } 338 | 339 | $cmd = sprintf($cmd, $processName); 340 | 341 | $output = $this->output; 342 | 343 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 344 | $process->run(function($type, $line) use ($output) { 345 | $output->write($line); 346 | }); 347 | } 348 | 349 | public function listenToPort($port) 350 | { 351 | if ($this->isWindows()) { 352 | $cmd = 'netstat -ano|findstr :%d'; 353 | } else { 354 | $cmd = 'netstat -tulpn | grep :%d'; 355 | } 356 | 357 | $cmd = sprintf($cmd, $port); 358 | 359 | $output = new BufferedOutput(); 360 | 361 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 362 | $process->run(function($type, $line) use ($output) { 363 | $output->write($line); 364 | }); 365 | 366 | return $output->fetch(); 367 | 368 | } 369 | 370 | public function getPidFromListeningToPort($port) 371 | { 372 | $listenToPort = $this->listenToPort($port); 373 | if (!empty($listenToPort)) { 374 | if ($this->isWindows()) { 375 | preg_match('/LISTENING[\s]+([0-9]+)/is', $listenToPort, $matches); 376 | } else { 377 | preg_match('/LISTEN[\s]+([0-9]+)/is', $listenToPort, $matches); 378 | } 379 | return isset($matches[1]) ? $matches[1] : null; 380 | } 381 | return null; 382 | } 383 | 384 | public function makeExecutable($file) 385 | { 386 | if ($this->isWindows()) { 387 | $cmd = null; 388 | } else { 389 | $cmd = 'chmod +x %s'; 390 | } 391 | 392 | if (!is_null($cmd)) { 393 | $output = $this->output; 394 | 395 | $cmd = sprintf($cmd, $file); 396 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 397 | $process->run(function($type, $line) use ($output) { 398 | $output->write($line); 399 | }); 400 | } 401 | } 402 | 403 | public function startSeleniumProcess() 404 | { 405 | // @todo Refactor this in 5.0; split binaries and drivers; Add Opera. 406 | // @see https://github.com/bogdananton/Selenium-Setup/issues/12 407 | $cmdExtra = ''; 408 | if ($binary = $this->config->getBinary('chromedriver.' . $this->getOsName() . '.' . $this->getOsType())) { 409 | $cmdExtra .= sprintf(' -Dwebdriver.chrome.driver=%s', $this->config->getBuildPath() . DIRECTORY_SEPARATOR . $binary->getBinName()); 410 | } 411 | if ($binary = $this->config->getBinary('iedriver.' . $this->getOsName() . '.' . $this->getOsType())) { 412 | $cmdExtra .= sprintf(' -Dwebdriver.ie.driver=%s', $this->config->getBuildPath() . DIRECTORY_SEPARATOR . $binary->getBinName()); 413 | } 414 | if ($binary = $this->config->getBinary('phantomjs.' . $this->getOsName() . '.' . $this->getOsType())) { 415 | $cmdExtra .= sprintf(' -Dphantomjs.binary.path=%s', $this->config->getBuildPath() . DIRECTORY_SEPARATOR . $binary->getBinName()); 416 | } 417 | 418 | if ($this->isWindows()) { 419 | $cmd = 'start /b java -jar %s -port %s -Dhttp.proxyHost=%s -Dhttp.proxyPort=%s -log %s %s'; 420 | } else { 421 | $cmd = 'java -jar %s -port %s -Dhttp.proxyHost=%s -Dhttp.proxyPort=%s -log %s %s >/dev/null 2>&1 &'; 422 | 423 | } 424 | 425 | $cmd = vsprintf($cmd, [ 426 | 'binary' => $this->config->getBuildPath() . DIRECTORY_SEPARATOR . $this->config->getBinary('selenium')->getBinName(), 427 | 'port' => $this->config->getPort(), 428 | 'proxyHost' => $this->config->getProxyHost(), 429 | 'proxyPort' => $this->config->getProxyPort(), 430 | 'log' => $this->config->getLogsPath() . DIRECTORY_SEPARATOR . 'selenium.log', 431 | 'cmdExtra' => $cmdExtra 432 | ]); 433 | 434 | //var_dump($cmd); 435 | 436 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 437 | $process->start(); 438 | // $process->getOutput(); 439 | return $process->getPid(); 440 | } 441 | 442 | public function hasXvfb() 443 | { 444 | $cmd = 'which Xvfb'; 445 | 446 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 447 | $process->start(); 448 | return ($process->getOutput() != '' ? true : false); 449 | } 450 | 451 | public function startDisplayProcess() 452 | { 453 | if ($this->isWindows()) { 454 | $cmd = null; 455 | } else { 456 | if ($this->getEnvVar('DISPLAY')) { 457 | return true; 458 | } 459 | $this->setEnvVar('DISPLAY', ':99.0'); 460 | $cmd = '/sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16'; 461 | } 462 | $output = new BufferedOutput(); 463 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 464 | $process->run(function($type, $line) use ($output) { 465 | $output->write($line); 466 | }); 467 | //var_dump($process->getPid()); 468 | return $process->getPid(); 469 | } 470 | 471 | public function getChromeVersion() 472 | { 473 | if ($this->isWindows()) { 474 | $cmd = 'reg query HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Google\Update\Clients\{8A69D345-D564-463c-AFF1-A69D9E530F96} | findstr /i pv'; 475 | $match = '/REG_SZ[\s]+([0-9.]+)/is'; 476 | } else { 477 | $cmd = 'google-chrome --version'; 478 | $match = '/Google Chrome ([0-9.]+)/is'; 479 | } 480 | 481 | $output = new BufferedOutput(); 482 | 483 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 484 | $process->run(function($type, $line) use ($output) { 485 | $output->write($line); 486 | }); 487 | 488 | preg_match($match, $output->fetch(), $matches); 489 | 490 | return isset($matches[1]) ? $matches[1] : null; 491 | } 492 | 493 | public function getFirefoxVersion() 494 | { 495 | if ($this->isWindows()) { 496 | $cmd = 'reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Mozilla\Mozilla Firefox" | findstr /i CurrentVersion'; 497 | $match = '/REG_SZ[\s]+([0-9.]+)/is'; 498 | } else { 499 | $cmd = 'firefox --version'; 500 | $match = '/Mozilla Firefox ([0-9.]+)/is'; 501 | } 502 | $output = new BufferedOutput(); 503 | 504 | $process = new Process($cmd, SeleniumSetup::$APP_ROOT_PATH, SeleniumSetup::$APP_PROCESS_ENV, null, null); 505 | $process->run(function($type, $line) use ($output) { 506 | $output->write($line); 507 | }); 508 | 509 | preg_match($match, $output->fetch(), $matches); 510 | 511 | return isset($matches[1]) ? $matches[1] : null; 512 | } 513 | 514 | } 515 | -------------------------------------------------------------------------------- /lib/FileSystem.php: -------------------------------------------------------------------------------- 1 | isFile($fileFullPath)) { 50 | touch($fileFullPath); 51 | } 52 | $this->writeToFile($fileFullPath, $contents); 53 | } 54 | 55 | /** 56 | * @param $fileFullPath 57 | * @return bool 58 | */ 59 | public function isWritable($fileFullPath) 60 | { 61 | if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) { 62 | $handler = @fopen($fileFullPath, 'a'); 63 | if (!$handler) { 64 | return false; 65 | } 66 | fclose($handler); 67 | return true; 68 | } else { 69 | if (!file_exists($fileFullPath)) { 70 | return true; 71 | } 72 | return @is_writable($fileFullPath); 73 | } 74 | } 75 | 76 | /** 77 | * @param $fileFullPath 78 | * @param string $contents 79 | * @return bool 80 | */ 81 | public function writeToFile($fileFullPath, $contents = '') 82 | { 83 | if (!$this->isWritable($fileFullPath)) { 84 | throw new \RuntimeException(sprintf('File %s is not writable.', $fileFullPath)); 85 | } 86 | 87 | $handler = fopen($fileFullPath, 'w'); 88 | 89 | if (!$handler) { 90 | throw new \RuntimeException(sprintf('Cannot open %s file.', $fileFullPath)); 91 | } 92 | 93 | $write = fwrite($handler, $contents); 94 | 95 | if ($write === false) { 96 | throw new \RuntimeException(sprintf('Cannot write to %s file.', $fileFullPath)); 97 | } 98 | 99 | fclose($handler); 100 | 101 | return true; 102 | } 103 | 104 | /** 105 | * @param $fileFullPath 106 | * @return string 107 | */ 108 | public function readFile($fileFullPath) 109 | { 110 | $handler = fopen($fileFullPath, 'r'); 111 | 112 | if (!$handler) { 113 | throw new \RuntimeException(sprintf('Cannot open %s file.', $fileFullPath)); 114 | } 115 | 116 | $contents = fread($handler, filesize($fileFullPath)); 117 | 118 | return $contents; 119 | } 120 | 121 | /** 122 | * @param $fileFullPath 123 | * @return resource 124 | */ 125 | public function openFileForReading($fileFullPath) 126 | { 127 | return fopen($fileFullPath, 'r'); 128 | } 129 | 130 | /** 131 | * @param $handler 132 | * @param int $limit 133 | * @param string $separator 134 | * @return array 135 | */ 136 | public function readFileLineAsCsv($handler, $limit = 0, $separator = '|') 137 | { 138 | return fgetcsv($handler, $limit, $separator); 139 | } 140 | 141 | public function rename($from, $to) 142 | { 143 | return rename($from, $to); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lib/FileSystemInterface.php: -------------------------------------------------------------------------------- 1 | fileSystem = new FileSystem(); 18 | } 19 | 20 | public function openLockFile() 21 | { 22 | if (!$this->fileSystem->isFile(SeleniumSetup::$APP_ROOT_PATH . DIRECTORY_SEPARATOR . SeleniumSetup::DEFAULT_LOCK_FILENAME)) { 23 | return false; 24 | } 25 | 26 | $contents = $this->fileSystem->readFile(SeleniumSetup::$APP_ROOT_PATH . DIRECTORY_SEPARATOR . SeleniumSetup::DEFAULT_LOCK_FILENAME); 27 | $lockerRaw = json_decode($contents); 28 | foreach ($lockerRaw as $serverObj) { 29 | $this->addServer(ServerItemFactory::createFromObj($serverObj)); 30 | } 31 | return true; 32 | } 33 | 34 | public function writeToLockFile() 35 | { 36 | $this->fileSystem->writeToFile( 37 | SeleniumSetup::$APP_ROOT_PATH . DIRECTORY_SEPARATOR . SeleniumSetup::DEFAULT_LOCK_FILENAME, 38 | $this->toJson() 39 | ); 40 | 41 | return true; 42 | } 43 | 44 | public function getServer($name) 45 | { 46 | if (!isset($this->locker[$name])) { 47 | throw new \Exception('Unknown server.'); 48 | } 49 | 50 | return $this->locker[$name]; 51 | } 52 | 53 | public function getServers() 54 | { 55 | return $this->locker; 56 | } 57 | 58 | public function addServer(ServerItem $server) 59 | { 60 | $this->locker[$server->getName()] = $server; 61 | } 62 | 63 | public function emptyLocker() 64 | { 65 | $this->locker = []; 66 | } 67 | 68 | public function toArray() 69 | { 70 | $result = []; 71 | foreach ($this->locker as $server) { 72 | $result[$server->getName()] = $server->toArray(); 73 | } 74 | return $result; 75 | } 76 | 77 | public function toJson() 78 | { 79 | $result = []; 80 | foreach ($this->locker as $server) { 81 | $result[$server->getName()] = $server->toArray(); 82 | } 83 | 84 | return json_encode($result, JSON_PRETTY_PRINT); 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /lib/Locker/ServerItem.php: -------------------------------------------------------------------------------- 1 | name; 21 | } 22 | 23 | /** 24 | * @param mixed $name 25 | * @return ServerItem 26 | */ 27 | public function setName($name) 28 | { 29 | $this->name = $name; 30 | return $this; 31 | } 32 | 33 | /** 34 | * @return mixed 35 | */ 36 | public function getDateStarted() 37 | { 38 | return $this->dateStarted; 39 | } 40 | 41 | /** 42 | * @param mixed $dateStarted 43 | * @return ServerItem 44 | */ 45 | public function setDateStarted($dateStarted) 46 | { 47 | $this->dateStarted = $dateStarted; 48 | return $this; 49 | } 50 | 51 | /** 52 | * @return mixed 53 | */ 54 | public function getDateStopped() 55 | { 56 | return $this->dateStopped; 57 | } 58 | 59 | /** 60 | * @param mixed $dateStopped 61 | * @return ServerItem 62 | */ 63 | public function setDateStopped($dateStopped) 64 | { 65 | $this->dateStopped = $dateStopped; 66 | return $this; 67 | } 68 | 69 | /** 70 | * @return mixed 71 | */ 72 | public function getPid() 73 | { 74 | return $this->pid; 75 | } 76 | 77 | /** 78 | * @param mixed $pid 79 | * @return ServerItem 80 | */ 81 | public function setPid($pid) 82 | { 83 | $this->pid = $pid; 84 | return $this; 85 | } 86 | 87 | /** 88 | * @return mixed 89 | */ 90 | public function getPort() 91 | { 92 | return $this->port; 93 | } 94 | 95 | /** 96 | * @param mixed $port 97 | * @return ServerItem 98 | */ 99 | public function setPort($port) 100 | { 101 | $this->port = $port; 102 | return $this; 103 | } 104 | 105 | /** 106 | * @return mixed 107 | */ 108 | public function getConfigFilePath() 109 | { 110 | return $this->configFilePath; 111 | } 112 | 113 | /** 114 | * @param mixed $configFilePath 115 | * @return ServerItem 116 | */ 117 | public function setConfigFilePath($configFilePath) 118 | { 119 | $this->configFilePath = $configFilePath; 120 | return $this; 121 | } 122 | 123 | public function toArray() 124 | { 125 | return (array)get_object_vars($this); 126 | } 127 | 128 | /** 129 | * @return array 130 | */ 131 | public static function getAllProperties() 132 | { 133 | return array_keys(get_object_vars(new self)); 134 | } 135 | 136 | } -------------------------------------------------------------------------------- /lib/Locker/ServerItemFactory.php: -------------------------------------------------------------------------------- 1 | name) || 10 | !isset($server->pid) || 11 | !isset($server->port) || 12 | !isset($server->configFilePath) 13 | ) { 14 | throw new \Exception('Server instance cannot be created. Missing keys.'); 15 | } 16 | 17 | return (new ServerItem()) 18 | ->setName($server->name) 19 | ->setPid($server->pid) 20 | ->setPort($server->port) 21 | ->setConfigFilePath($server->configFilePath) 22 | ->setDateStarted(date(ServerItem::DATE_FORMAT)); 23 | } 24 | 25 | /** 26 | * The PID is no longer required to be greater than zero. (when PID = zero, this usually means it's stopped). 27 | * 28 | * @param string $name 29 | * @param int $pid 30 | * @param int $port 31 | * @param string $configFilePath 32 | * 33 | * @return ServerItem 34 | * @throws \Exception 35 | */ 36 | public static function createFromProperties($name, $pid, $port, $configFilePath) 37 | { 38 | if ( 39 | empty($name) || 40 | // empty($pid) || 41 | empty($port) || 42 | empty($configFilePath) 43 | ) { 44 | throw new \Exception('Server instance cannot be created. Missing values.'); 45 | } 46 | 47 | return (new ServerItem()) 48 | ->setName($name) 49 | ->setPid($pid) 50 | ->setPort($port) 51 | ->setConfigFilePath($configFilePath) 52 | ->setDateStarted(date(ServerItem::DATE_FORMAT)); 53 | } 54 | } -------------------------------------------------------------------------------- /lib/SeleniumSetup.php: -------------------------------------------------------------------------------- 1 | setDefaultCommand(self::APP_DEFAULT_COMMAND); 46 | 47 | $dispatcher = new EventDispatcher(); 48 | $dispatcher->addListener(ConsoleEvents::COMMAND, function(ConsoleCommandEvent $event) { 49 | // get the input instance 50 | // $input = $event->getInput(); 51 | 52 | // get the output instance 53 | $output = $event->getOutput(); 54 | 55 | // get the command to be executed 56 | $command = $event->getCommand(); 57 | 58 | // write something about the command 59 | //$output->writeln(sprintf('Before running command %s', $command->getName())); 60 | if ($command->getName() == StartServer::CLI_COMMAND) { 61 | $output->write(self::$BANNER); 62 | } 63 | 64 | // get the application 65 | // $application = $command->getApplication(); 66 | }); 67 | $console->setDispatcher($dispatcher); 68 | 69 | $console->addCommands([ 70 | new Controller\StartServer, 71 | new Controller\StopServer, 72 | new Controller\ListServers, 73 | new Controller\RegisterServer, 74 | ]); 75 | 76 | $console->run(); 77 | } 78 | } -------------------------------------------------------------------------------- /lib/Service/AbstractService.php: -------------------------------------------------------------------------------- 1 | config = $config; 27 | $this->fileSystem = new FileSystem(); 28 | $this->env = $env; 29 | $this->locker = new Locker(); 30 | $this->input = $input; 31 | $this->output = $output; 32 | } 33 | 34 | public function handle() 35 | { 36 | throw new \Exception('A service must implement the handle() method.'); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /lib/Service/ListServersService.php: -------------------------------------------------------------------------------- 1 | env->test(); 26 | } 27 | 28 | public function handle() 29 | { 30 | $instanceName = $this->input->getArgument('name'); 31 | $instancePort = $this->input->getArgument('port'); 32 | 33 | $this->locker->openLockFile(); 34 | $status = $this->validate($instanceName, $instancePort); 35 | 36 | $this->logStatus($status, $instanceName, $instancePort); 37 | 38 | if ($status === self::VALID_INSTANCE) { 39 | $this->register($instanceName, $instancePort); 40 | $this->log(self::SUCCESS_MESSAGE_ADDED, $instanceName); 41 | } 42 | } 43 | 44 | protected function register($name, $port) 45 | { 46 | // Find filename by instance name. 47 | $filename = $this->getInstanceConfigFilename($name); 48 | 49 | // Update configuration to new instance clone. 50 | $this->config->setName($name); 51 | $this->config->setPort($port); 52 | $this->config->setFilePath($filename); 53 | 54 | // Store new filename using the default instance template. 55 | $this->fileSystem->createFile($filename, $this->config->toJson()); 56 | 57 | // Append instance entry in lock-file. 58 | $this->locker->addServer( 59 | ServerItemFactory::createFromProperties( 60 | $name, 61 | 0, 62 | $port, 63 | $filename 64 | ) 65 | ); 66 | 67 | // Write lock-file settings. 68 | $this->locker->writeToLockFile(); 69 | } 70 | 71 | protected function getInstanceConfigFilename($name) 72 | { 73 | return dirname($this->config->getFilePath()) . DIRECTORY_SEPARATOR . $name . '.json'; 74 | } 75 | 76 | /** 77 | * - initialize OK status. 78 | * 79 | * - for each registered instances 80 | * - if equal name as the new instance, append (multiply) the value of INVALID_NAME 81 | * - if equal port as the new instance, append (multiply) the value of INVALID_PORT 82 | * 83 | * - return status final value 84 | * 85 | * @return int 86 | */ 87 | protected function validate($testName, $testPort) 88 | { 89 | $status = self::VALID_INSTANCE; 90 | 91 | $instances = $this->locker->getServers(); 92 | 93 | foreach ($instances as $instance) { 94 | if ($instance->getName() == $testName) { 95 | $status *= self::INVALID_NAME; 96 | } 97 | 98 | if ($instance->getPort() == $testPort) { 99 | $status *= self::INVALID_PORT; 100 | } 101 | } 102 | 103 | return $status; 104 | } 105 | 106 | protected function logStatus($status, $name, $port) 107 | { 108 | if ($this->isStatusContaining($status, self::INVALID_NAME)) { 109 | $this->log(self::ERROR_MESSAGE_NAME, $name, self::LOG_TYPE_ERROR); 110 | } 111 | 112 | if ($this->isStatusContaining($status, self::INVALID_PORT)) { 113 | $this->log(self::ERROR_MESSAGE_PORT, $port, self::LOG_TYPE_ERROR); 114 | } 115 | } 116 | 117 | protected function isStatusContaining($status, $checkedStatus) 118 | { 119 | return $status % $checkedStatus === 0; 120 | } 121 | 122 | protected function log($message, $value = null, $type = self::LOG_TYPE_INFO) { 123 | $this->output->writeln(sprintf('<' . $type . '>' . $message . '', $value)); 124 | } 125 | } -------------------------------------------------------------------------------- /lib/Service/StartServerService.php: -------------------------------------------------------------------------------- 1 | fileSystem->isDir(($this->config->getBuildPath()))) { 12 | $this->fileSystem->createDir($this->config->getBuildPath()); 13 | } 14 | 15 | // Create the tmp folder. 16 | if (!$this->fileSystem->isDir(($this->config->getTmpPath()))) { 17 | $this->fileSystem->createDir($this->config->getTmpPath()); 18 | } 19 | 20 | // Create the logs folder. 21 | if (!$this->fileSystem->isDir($this->config->getLogsPath())) { 22 | $this->fileSystem->createDir($this->config->getLogsPath()); 23 | } 24 | } 25 | 26 | protected function downloadDrivers() 27 | { 28 | foreach ($this->config->getBinaries() as $binary) { 29 | // Skip binaries that don't belong to the current operating system. 30 | if ( 31 | !is_null($binary->getOs()) && $binary->getOs() != $this->env->getOsName() || 32 | !is_null($binary->getOsType()) && $binary->getOsType() != $this->env->getOsType() 33 | ) { 34 | continue; 35 | } 36 | 37 | $binaryPath = $this->config->getBuildPath() . DIRECTORY_SEPARATOR . $binary->getBinName(); 38 | 39 | if (!$this->fileSystem->isFile($binaryPath)) { 40 | $this->output->writeln( 41 | sprintf('Downloading %s %s ...', $binary->getLabel(), $binary->getVersion()) 42 | ); 43 | 44 | // Download. 45 | $downloadTo = $this->config->getBuildPath() . DIRECTORY_SEPARATOR . pathinfo($binary->getDownloadUrl(), PATHINFO_BASENAME); 46 | $download = $this->env->download($binary->getDownloadUrl(), $downloadTo); 47 | $this->output->writeln($download); 48 | 49 | // Unzip. 50 | if (in_array(pathinfo($binary->getDownloadUrl(), PATHINFO_EXTENSION), ['zip', 'tar', 'tar.gz'])) { 51 | $zip = new \ZipArchive; 52 | $res = $zip->open($downloadTo); 53 | if ($res === true) { 54 | $zip->extractTo( 55 | $this->config->getBuildPath(), 56 | [$binary->getBinName()] 57 | ); 58 | $zip->close(); 59 | } 60 | } else { 61 | $this->fileSystem->rename($downloadTo, $this->config->getBuildPath() . DIRECTORY_SEPARATOR . $binary->getBinName()); 62 | } 63 | 64 | // Make executable. 65 | $this->env->makeExecutable($this->config->getBuildPath() . DIRECTORY_SEPARATOR . $binary->getBinName()); 66 | } else { 67 | $this->output->writeln( 68 | sprintf('Skipping %s %s. Binary already exists.', $binary->getLabel(), $binary->getVersion()) 69 | ); 70 | } 71 | } 72 | } 73 | 74 | public function test() 75 | { 76 | return $this->env->test(); 77 | } 78 | 79 | public function handle() 80 | { 81 | // @todo should allow only registered instances to be called 82 | 83 | $this->createFolders(); 84 | $this->downloadDrivers(); 85 | 86 | // Add build folder to path. 87 | $this->env->addPathToGlobalPath($this->config->getBuildPath()); 88 | 89 | // Warn if Chrome or Firefox binaries are not available. 90 | if ($chromeVersion = $this->env->getChromeVersion()) { 91 | $this->output->writeln('Chrome binary found, v.' . $chromeVersion); 92 | } else { 93 | $this->output->writeln('WARNING: Chrome binary not found.'); 94 | } 95 | 96 | if ($firefoxVersion = $this->env->getFirefoxVersion()) { 97 | $this->output->writeln('Firefox binary found, v.' . $firefoxVersion); 98 | } else { 99 | $this->output->writeln('WARNING: Firefox binary not found.'); 100 | } 101 | 102 | // Warn if display is not available. 103 | if ($this->env->isLinux()) { 104 | if ($this->env->hasXvfb()) { 105 | $this->output->writeln('Xvfb is installed. Good.'); 106 | } else { 107 | $this->output->writeln('WARNING: Xvfb is not installed.'); 108 | } 109 | } 110 | 111 | // $pid = $this->env->startDisplayProcess(); 112 | 113 | // Start Selenium Server instance. 114 | $this->output->writeln( 115 | sprintf('Starting Selenium Server (%s) ... %s:%s', $this->config->getName(), $this->config->getHostname(), $this->config->getPort()) 116 | ); 117 | 118 | $pid = $this->env->startSeleniumProcess(); 119 | if ($pid > 0) { 120 | // Make sure that we capture the right PID. 121 | while ($this->env->listenToPort($this->config->getPort()) == '') { 122 | $this->output->write('.'); 123 | } 124 | $parentPid = $this->env->getPidFromListeningToPort($this->config->getPort()); 125 | if ($parentPid) { 126 | $pid = $parentPid; 127 | } 128 | 129 | // @todo open the lock file only to update the status and ports, the instance was already added. 130 | $this->locker->openLockFile(); 131 | $this->locker->addServer( 132 | ServerItemFactory::createFromProperties( 133 | $this->config->getName(), 134 | $pid, 135 | $this->config->getPort(), 136 | $this->config->getFilePath() 137 | ) 138 | ); 139 | $this->locker->writeToLockFile(); 140 | } 141 | 142 | $this->output->writeln('Done'); 143 | $this->output->writeln( 144 | sprintf('Test it at http://%s:%s/wd/hub/', $this->config->getHostname(), $this->config->getPort()) 145 | ); 146 | } 147 | } -------------------------------------------------------------------------------- /lib/Service/StopServerService.php: -------------------------------------------------------------------------------- 1 | locker->openLockFile(); 9 | $serverItem = $this->locker->getServer($this->input->getArgument('name')); 10 | $this->env->killProcessByPid($serverItem->getPid()); 11 | } 12 | } -------------------------------------------------------------------------------- /selenium-setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 15 | --------------------------------------------------------------------------------