├── .env ├── .gitattribute ├── .gitignore ├── .travis.yml ├── CNAME ├── LICENSE ├── README.md ├── _config.yml ├── artisan ├── bootstrap ├── app.php └── config.php ├── composer.json ├── phpunit.xml ├── server.php ├── src ├── Console │ ├── BootSelenium.php │ ├── GetWebDriver.php │ ├── Kernel.php │ └── MakeSeleniumTestCommand.php ├── Exceptions │ ├── CannotClickElement.php │ ├── CannotFindElement.php │ └── Handler.php ├── SeleniumServiceProvider.php ├── SeleniumTestCase.php ├── Services │ ├── InteractWithPage.php │ ├── ManageWindow.php │ ├── WaitForElement.php │ └── WorkWithDatabase.php ├── TestingMiddleware.php └── Traits │ └── WebDriverUtilsTrait.php ├── steward.yml ├── storage ├── tests │ └── .keep └── views │ └── .keep ├── stubs ├── public │ └── index.php ├── test.stub └── views │ ├── about.blade.php │ ├── contact.blade.php │ ├── form.blade.php │ ├── index.blade.php │ ├── partial │ └── nav.blade.php │ └── success.blade.php ├── testing.env └── tests └── feature ├── Console └── GetSeleniumServerTest.php ├── FormTest.php └── InteractionTest.php /.env: -------------------------------------------------------------------------------- 1 | APP_TESTING_URL="hello-world" -------------------------------------------------------------------------------- /.gitattribute: -------------------------------------------------------------------------------- 1 | * text=auto 2 | _config.yml 3 | CNAME 4 | artisan 5 | tests 6 | logs 7 | CHANGELOG.md export-ignore 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | build 4 | logs/* 5 | storage/logs/* 6 | storage/views/* 7 | storage/tests/* 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | addons: 4 | sauce_connect: true 5 | 6 | php: 7 | - '7.1' 8 | 9 | install: 10 | # Download ngrok 11 | - curl -sO https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip 12 | - unzip $PWD/ngrok-stable-linux-amd64.zip 13 | - chmod +x $PWD/ngrok 14 | 15 | # Download json parser for determining ngrok tunnel 16 | - curl -sO http://stedolan.github.io/jq/download/linux64/jq 17 | - chmod +x $PWD/jq 18 | 19 | # Setup project requirements 20 | - composer install 21 | - composer dump -o 22 | 23 | cache: 24 | directories: 25 | - $HOME/.composer/cache 26 | 27 | before_script: 28 | - php artisan serve > /dev/null & 29 | 30 | # Open ngrok tunnel 31 | - $PWD/ngrok authtoken $NGROK_TOKEN 32 | - $PWD/ngrok http 8000 > /dev/null & 33 | 34 | # sleep to allow ngrok to initialize 35 | - sleep 2 36 | 37 | # extract the ngrok url and put it into testing.env file 38 | - echo "APP_URL=$(curl -s localhost:4040/api/tunnels/command_line | jq --raw-output .public_url)" > testing.env 39 | 40 | script: > 41 | vendor/bin/steward run testing chrome -vv 42 | --server-url="http://$SAUCE_USERNAME:$SAUCE_ACCESS_KEY@ondemand.saucelabs.com/wd/hub" 43 | --capability="browserName:chrome" 44 | --capability="platform:Windows 8.1" 45 | --capability="chromedriverVersion:'2.35'" 46 | 47 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | selenium.mudasir.me -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Shaikh Mohammed Mudasir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Laravel 5.x Testing for Selenium made easy. 3 |

4 | 5 |

6 | 7 | Code Climate 8 | StyleCI 9 | Latest Stable Version 10 | Total Downloads 11 | License 12 |

13 | 14 | ## Key Points: 15 | 1. You don't need to download anything except this package. 16 | 2. This package download the selenium standalone server v3.11.0 by default and chrome driver will be downloaded based on operating system. 17 | 3. Fluit API based on [Browser Testing Kit](https://github.com/laravel/browser-kit-testing) 18 | 3. Has a minimum configuration option and many things are pulled from the Laravel default configuration. 19 | 20 | | Version | Package Version | 21 | | ------------ | --------------- | 22 | | Laravel 5.6 | 2.0 | 23 | | Laravel 5.* | 1.0 | 24 | | PHP 7.1 | 2.0 | 25 | 26 | ## Requirements: 27 | 1. Java should be installed on local machine. 28 | 2. You should have at least basic understanding of PHPUnit. 29 | 30 | 31 | ## Installation guide: 32 | Installing with [Laravel Package Manager](https://github.com/Qafeen/Manager) then you can install it by running given command and Manager will take care to register selenium service provider. 33 | ```php 34 | php artisan add modelizer/selenium 35 | ``` 36 | 37 | Or you can do it by composer. 38 | ```php 39 | composer require modelizer/selenium "~2.0" 40 | ``` 41 | 42 | Register service provider in `app.php` 43 | ```php 44 | Modelizer\Selenium\SeleniumServiceProvider::class 45 | ``` 46 | 47 | Working with environment variables: 48 | You need to create sperate file `testing.env` in root directory to load testing specific variable. example 49 | ``` 50 | APP_URL=http://testing.dev:8000 51 | ``` 52 | 53 | Don't forget to clear laravel configuration cache file. 54 | ```php 55 | php artisan config:clear 56 | ``` 57 | 58 | We are done! Lets start the selenium server. 59 | ```php 60 | php artisan selenium:start 61 | ``` 62 | 63 | ## Create first test: 64 | 65 | Via an Artisan command 66 | 67 | ```php 68 | php artisan selenium:make:test SeleniumExampleTest 69 | ``` 70 | 71 | Manually 72 | 73 | 1. Create a dummy `SeleniumExampleTest.php` file in `tests` directory. 74 | 2. Add this code to `SeleniumExampleTest.php` file and run phpunit `vendor/bin/phpunit tests/SeleniumExampleTest.php` 75 | ```php 76 | visit('/') 93 | ->see('Laravel') 94 | ->hold(3); 95 | } 96 | 97 | /** 98 | * A basic submission test example. 99 | * 100 | * @return void 101 | */ 102 | public function testLoginFormExample() 103 | { 104 | $loginInput = [ 105 | 'username' => 'dummy-name', 106 | 'password' => 'dummy-password' 107 | ]; 108 | 109 | // Login form test case scenario 110 | $this->visit('/login') 111 | ->submitForm('#login-form', $loginInput) 112 | ->see('Welcome'); // Expected Result 113 | } 114 | } 115 | ``` 116 | 117 | ## Run the test cases 118 | ```php 119 | vendor/bin/steward run staging chrome 120 | ``` 121 | 122 | This package is been build on top of [Steward](https://github.com/lmc-eu/steward/) for running test case with specific arguments you can check out [Steward's Wiki](https://github.com/lmc-eu/steward/wiki/Run-only-specified-tests) 123 | 124 | For full documentation you can checkout our [API wiki](https://github.com/Modelizer/Laravel-Selenium/wiki/APIs). Which internally inherit [facebook Web Driver](https://github.com/facebook/php-webdriver) so you can liverage full functionality of these dependency packages. 125 | 126 | ## Notes: 127 | 1. Selenium 3.11.0 and ChromeDriver 2.35 is been used. 128 | 2. Feel free to contribute or create an issue. 129 | 3. The user will not be able to swap between PHPUnit and Selenium who are below Laravel 5.3. 130 | 4. We made changelog as [release board](https://github.com/Modelizer/Laravel-Selenium/releases) and [wiki](https://github.com/Modelizer/Selenium/wiki/change-log). 131 | 5. If a virtual machine is being used such as VirtualBox (Vagrant, Homestead), a framebuffer is needed: 132 | 133 | ```bash 134 | # install xvfb if needed: 135 | sudo apt-get install xvfb 136 | 137 | # run Xvfb 138 | sudo nohup Xvfb :10 -ac 139 | 140 | # Set DISPLAY environment variable 141 | export DISPLAY=:10 142 | ``` 143 | 144 | ## Roadmap: 145 | 1. ~~Firefox support added.~~ (Note: Only work when user has installed firefox locally) 146 | 2. ~~Windows and Linux support needs to be added.~~ 147 | 3. ~~Drivers files should get downloaded as per user-specific operating system.~~ 148 | 4. Add more support for more API. 149 | 5. Support for multiple browser. 150 | 6. Behat integration if possible (research) 151 | 7. Support for 3rd party services such as saucelab. 152 | 153 | ## Summary: 154 | Many APIs such as `see`, `wait`, `submitForm` etc are been implemented in Laravel 5.3, and the whole goal of this package is to make it easier for the user to swap testing type anytime. 155 | Eg: If a user wants to test by selenium then he only need to extend `Modelizer\Selenium\SeleniumTestCase` in his test case or if he wants to do PHPUnit testing then he will be able to do it by extending `TestCase` which Laravel 5.3 provide by default. This will help the user to test a case in many different testing types without doing any changes with API. 156 | 157 | 158 | 159 | ## Contribution: 160 | 1. If you like this package you can give it a star. 161 | 2. Help to keep readme up to date with some functionality which exist in this package but not visible to other. 162 | 3. Feel free to create PR or Issues or suggestion which can help this package to grow. 163 | Just do it. You are welcome :) 164 | 165 | 166 | 167 | ## Credits 168 | 169 | | Contributors | Twitter | Ask for Help | Site | 170 | |------------------------|-----------|--------------|------| 171 | | [Mohammed Mudassir](https://github.com/Modelizer) (Creator) | @[md_mudasir](https://twitter.com/md_mudasir) | hello@mudasir.me | [http://mudasir.me](http://mudasir.me/) | 172 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Modelizer\Selenium\Console\Kernel::class); 25 | 26 | $status = $kernel->handle( 27 | $input = new Symfony\Component\Console\Input\ArgvInput, 28 | new Symfony\Component\Console\Output\ConsoleOutput 29 | ); 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Shutdown The Application 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Once Artisan has finished running, we will fire off the shutdown events 37 | | so that any final work may be done by the application before we shut 38 | | down the process. This is the last thing to happen to the request. 39 | | 40 | */ 41 | 42 | $kernel->terminate($input, $status); 43 | 44 | exit($status); -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 28 | Illuminate\Contracts\Http\Kernel::class, 29 | Orchestra\Testbench\Http\Kernel::class 30 | ); 31 | 32 | $app->singleton( 33 | Illuminate\Contracts\Console\Kernel::class, 34 | Modelizer\Selenium\Console\Kernel::class 35 | ); 36 | 37 | $app->singleton( 38 | Illuminate\Contracts\Debug\ExceptionHandler::class, 39 | Modelizer\Selenium\Exceptions\Handler::class 40 | ); 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Registering Service Provider 45 | |-------------------------------------------------------------------------- 46 | */ 47 | $app->register(Illuminate\Filesystem\FilesystemServiceProvider::class); 48 | $app->register(Illuminate\View\ViewServiceProvider::class); 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Registering paths and configuration 53 | |-------------------------------------------------------------------------- 54 | */ 55 | $app->instance('path.public', $config['view']['public']); 56 | $app->instance('config', new Illuminate\Config\Repository($config)); 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Loading filesystem 61 | |-------------------------------------------------------------------------- 62 | */ 63 | $app->singleton('files', function () { 64 | return new Illuminate\Filesystem\Filesystem(); 65 | }); 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Enabling Facade 70 | |-------------------------------------------------------------------------- 71 | */ 72 | Illuminate\Support\Facades\Facade::setFacadeApplication($app); 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Setting basic routes 77 | |-------------------------------------------------------------------------- 78 | */ 79 | app('router')->get('/{page?}', function ($page = 'index') { 80 | return view($page); 81 | }); 82 | 83 | return $app; 84 | -------------------------------------------------------------------------------- /bootstrap/config.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'public' => base_path('stubs/public'), 18 | 19 | 'paths' => [ 20 | base_path('stubs/views'), 21 | ], 22 | 23 | 'compiled' => storage_path('views'), 24 | ], 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Web Drivers 29 | | Note: filename denote when url is been complete after extracting then it 30 | | find the filename given below and rename it as per operating system and 31 | | and driver name. Example file would become now mac-chrome and in windows 32 | | win-chrome.exe 33 | | Special execution permission will be given for windows file. 34 | |-------------------------------------------------------------------------- 35 | */ 36 | 'web-drivers' => [ 37 | 'chrome' => [ 38 | 'mac' => [ 39 | 'version' => '2.35.0', 40 | 'url' => 'https://chromedriver.storage.googleapis.com/2.35/chromedriver_mac64.zip', 41 | 'filename' => 'chromedriver', 42 | ], 43 | 'win' => [ 44 | 'version' => '2.35.0', 45 | 'url' => 'https://chromedriver.storage.googleapis.com/2.35/chromedriver_win32.zip', 46 | 'filename' => 'chromedriver.exe', 47 | ], 48 | 'linux' => [ 49 | 'version' => '2.35.0', 50 | 'url' => 'https://chromedriver.storage.googleapis.com/2.35/chromedriver_linux64.zip', 51 | 'filename' => 'chromedriver', 52 | ], 53 | ], 54 | 'firefox' => [ 55 | 'mac' => [ 56 | 'version' => 'v0.20.0', 57 | 'url' => 'https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-macos.tar.gz', 58 | 'filename' => 'geckodriver', 59 | ], 60 | 61 | 'win' => [ 62 | 'version' => 'v0.20.0', 63 | 'url' => 'https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-win32.zip', 64 | 'filename' => 'geckodriver.exe', 65 | ], 66 | 'linux' => [ 67 | 'version' => 'v0.20.0', 68 | 'url' => 'https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux32.tar.gz', 69 | 'filename' => 'geckodriver', 70 | ], 71 | ], 72 | ], 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Default logging for minimized version of laravel created for testing 77 | |-------------------------------------------------------------------------- 78 | */ 79 | 'logging' => [ 80 | 'default' => 'single', 81 | 'channels' => [ 82 | 'single' => [ 83 | 'driver' => 'single', 84 | 'path' => storage_path('logs/selenium.log'), 85 | 'level' => 'debug', 86 | ], 87 | ], 88 | ], 89 | ]; 90 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modelizer/selenium", 3 | "description": "Selenium Package for laravel 5.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Mohammed Mudassir", 8 | "email": "hello@mudasir.me" 9 | } 10 | ], 11 | "collaborators": [ 12 | { 13 | "name": "John Hoopes", 14 | "email": "john.hoopes@madisoncreativeweb.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=7.1", 19 | "lmc/steward": "dev-master", 20 | "guzzlehttp/guzzle": "^6.2", 21 | "orchestra/testbench": "^3.3" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Modelizer\\Selenium\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "Modelizer\\Selenium\\Tests\\" : "tests/" 31 | } 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "providers": [ 36 | "Modelizer\\Selenium\\SeleniumServiceProvider" 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 'chrome', 21 | 'firefox' => 'gecko', 22 | 'edge' => 'edge', 23 | ]; 24 | 25 | /** 26 | * The name and signature of the console command. 27 | * 28 | * @var string 29 | */ 30 | protected $signature = 'selenium:start {driver=chrome : (chrome|firefox) Driver version} '. 31 | '{serverVersion=3.11.0 : Selenium Server Version} '; 32 | 33 | /** 34 | * The console command description. 35 | * 36 | * @var string 37 | */ 38 | protected $description = 'Boot Selenium Server.'; 39 | 40 | /** 41 | * Execute the console command. 42 | */ 43 | public function handle() 44 | { 45 | $cmd = collect(array_merge($this->getSeleniumDefaultCommand(), $this->getArguments())) 46 | ->except('driver', 'serverVersion') 47 | ->implode(' '); 48 | 49 | $this->info('Starting Selenium server v'.$this->argument('serverVersion')); 50 | $this->info("Using {$this->argument('driver')} driver"); 51 | 52 | echo shell_exec($cmd.' '.$this->getSeleniumOptions()); 53 | } 54 | 55 | /** 56 | * Get the default commands which are require to boot selenium server. 57 | * 58 | * @return array 59 | */ 60 | public function getSeleniumDefaultCommand() 61 | { 62 | return [ 63 | 'java', 64 | $this->getWebDriver(env('DEFAULT_BROWSER', $this->argument('driver'))), 65 | '-jar '.$this->getSeleniumServerQualifiedName(), 66 | ]; 67 | } 68 | 69 | /** 70 | * Get selenium server qualified location. 71 | * 72 | * @throws FileNotFoundException 73 | * 74 | * @return string 75 | */ 76 | public function getSeleniumServerQualifiedName() 77 | { 78 | $files = opendir($binDirectory = base_path('vendor/bin')); 79 | 80 | while (false !== ($file = readdir($files))) { 81 | if (str_contains($file, 'selenium') && str_contains($file, $this->argument('serverVersion'))) { 82 | return $binDirectory.DIRECTORY_SEPARATOR.$file; 83 | } 84 | } 85 | 86 | return $this->downloadSelenium(); 87 | } 88 | 89 | /** 90 | * Download and get the file name of selenium server. 91 | * 92 | * @return string selenium server file directory 93 | */ 94 | public function downloadSelenium() 95 | { 96 | $this->info('Downloading Selenium server file. Please wait...'); 97 | 98 | $process = new Process(base_path('vendor/bin/steward install '.$this->argument('serverVersion'))); 99 | $process->setTimeout(0); 100 | 101 | $process->run(); 102 | 103 | return $process->getOutput(); 104 | } 105 | 106 | /** 107 | * Get web driver full qualified location. 108 | * 109 | * @param $driverName 110 | * 111 | * @return string 112 | */ 113 | protected function getWebDriver($driverName) 114 | { 115 | $config = $this->getConfig(); 116 | 117 | if (empty($config)) { 118 | $this->warn('No web driver loaded.'); 119 | 120 | return ''; 121 | } 122 | 123 | $driver = base_path("vendor/bin/{$this->getFileName()}"); 124 | 125 | if (!is_file($driver)) { 126 | $this->call('selenium:web-driver:download', [ 127 | 'driver' => $driverName, 128 | ]); 129 | } 130 | 131 | return "-Dwebdriver.{$this->dWebDriver[$driverName]}.driver={$driver}"; 132 | } 133 | 134 | protected function getSeleniumOptions() 135 | { 136 | $options = []; 137 | 138 | if (!config('selenium')) { 139 | return ''; 140 | } 141 | 142 | foreach (config('selenium') as $key => $value) { 143 | $options[] = "-$key $value"; 144 | } 145 | 146 | return implode(' ', $options); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Console/GetWebDriver.php: -------------------------------------------------------------------------------- 1 | checkRequirements()->setConfig()->download($client)) { 38 | $this->error('Aborting...'); 39 | 40 | return false; 41 | } 42 | 43 | switch ($this->getDriver()) { 44 | case 'chrome': 45 | $this->unZip($resource); 46 | break; 47 | case 'firefox': 48 | $this->unTarGz($resource); 49 | break; 50 | } 51 | 52 | $this->info('Download complete.'); 53 | } 54 | 55 | /** 56 | * Check the requirements before downloading. 57 | * 58 | * @return $this|bool 59 | */ 60 | protected function checkRequirements() 61 | { 62 | if (!$this->getUserOS()) { 63 | $this->error("Configuration is not available for $this->userOS operation system."); 64 | 65 | exit; 66 | } 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Downloading driver. 73 | * 74 | * @param $client 75 | * 76 | * @return bool|string 77 | */ 78 | protected function download($client) 79 | { 80 | $resource = base_path('vendor/bin/driver-file'); 81 | 82 | if (is_file(base_path("vendor/bin/{$this->getFileName()}"))) { 83 | if (!$this->confirm(ucfirst($this->argument('driver')).' web driver file already exists. Would you still like to download it?')) { 84 | return false; 85 | } 86 | } 87 | 88 | $this->info("Downloading {$this->driver} web driver for {$this->userOS}..."); 89 | 90 | $resource .= $this->getExtension(); 91 | 92 | // Downloading Driver 93 | $client->request('get', $this->getConfig()['url'], [ 94 | 'save_to' => \GuzzleHttp\Psr7\stream_for(fopen($resource, 'w')), 95 | ]); 96 | 97 | return $resource; 98 | } 99 | 100 | /** 101 | * Unzip the web driver raw file. 102 | * 103 | * @param $resource 104 | * 105 | * @throws \ErrorException 106 | * 107 | * @return $this 108 | */ 109 | public function unZip(&$resource) 110 | { 111 | $this->info("Unzipping $resource file..."); 112 | 113 | $zip = new ZipArchive(); 114 | if (!$zip->open($resource)) { 115 | throw new \ErrorException("Unable to unzip downloaded file $resource."); 116 | } 117 | 118 | // Renaming chrome driver 119 | $zip->extractTo(dirname($resource)); 120 | $zip->close(); 121 | 122 | return $this->cleanUp($resource); 123 | } 124 | 125 | protected function unTarGz(&$resource) 126 | { 127 | // decompress from gz 128 | (new \PharData($resource))->decompress(); 129 | 130 | // Un-archive from the tar 131 | (new \PharData(dirname($resource).'/driver-file.tar'))->extractTo(dirname($resource)); 132 | 133 | return $this->cleanUp($resource); 134 | } 135 | 136 | protected function cleanUp(&$resource) 137 | { 138 | // New file will be saved to vendor/bin directory 139 | $newFileName = base_path('vendor/bin/'.$this->getFileName()); 140 | 141 | // Renaming file 142 | rename( 143 | base_path('vendor/bin/'.$this->getConfig()['filename']), 144 | $newFileName 145 | ); 146 | 147 | // make it executable for unix machine 148 | $this->userOS == 'win' ?: chmod($newFileName, 0744); 149 | 150 | // Delete the zip file 151 | unlink($resource); 152 | 153 | return $this; 154 | } 155 | 156 | public function getExtension() 157 | { 158 | switch ($this->getDriver()) { 159 | case 'chrome': 160 | return '.zip'; 161 | case 'firefox': 162 | return '.tar.gz'; 163 | } 164 | 165 | return ''; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | laravel->getNamespace(), '', $name); 54 | 55 | return $this->laravel['path.base'].'/tests/'.str_replace('\\', '/', $name).'.php'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Exceptions/CannotClickElement.php: -------------------------------------------------------------------------------- 1 | commands($this->commands); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SeleniumTestCase.php: -------------------------------------------------------------------------------- 1 | setUpTheTestEnvironment(); 47 | 48 | $this->baseUrl = env('APP_URL', $this->baseUrl); 49 | 50 | parent::setUp(); 51 | } 52 | 53 | /** 54 | * Clean up the testing environment before the next test. 55 | * 56 | * @return void 57 | */ 58 | protected function tearDown() 59 | { 60 | $this->tearDownTheTestEnvironment(); 61 | } 62 | 63 | /** 64 | * Boot the testing helper traits. 65 | * 66 | * @return array 67 | */ 68 | protected function setUpTraits() 69 | { 70 | return $this->setUpTheTestEnvironmentTraits(); 71 | } 72 | 73 | /** 74 | * Refresh the application instance. 75 | * 76 | * @return void 77 | */ 78 | protected function refreshApplication() 79 | { 80 | $this->app = $this->createApplication(); 81 | } 82 | 83 | /** 84 | * Define environment setup. 85 | * 86 | * @param \Illuminate\Foundation\Application $app 87 | * 88 | * @return void 89 | */ 90 | protected function getEnvironmentSetUp($app) 91 | { 92 | putenv('APP_ENV=testing'); 93 | $app->useEnvironmentPath($this->getRootDirectory()); 94 | $app->loadEnvironmentFrom('testing.env'); 95 | $app->bootstrapWith([LoadEnvironmentVariables::class]); 96 | } 97 | 98 | /** 99 | * Get root directory of a project or package 100 | * Note: $app->basePath() will not due to it is loading orchestra test package which is loading from 101 | * different directory and this application is been booted from steward. 102 | * 103 | * @return bool|string 104 | */ 105 | private function getRootDirectory() 106 | { 107 | return realpath(__DIR__.($this->installedAsPackage() ? '/../../../../' : '/../')); 108 | } 109 | 110 | /** 111 | * Check if the selenium package is been installed as dependency or a separate project. 112 | * 113 | * @return bool 114 | */ 115 | private function installedAsPackage() 116 | { 117 | return file_exists(__DIR__.'/../../../autoload.php'); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Services/InteractWithPage.php: -------------------------------------------------------------------------------- 1 | wd->get($this->baseUrl.$path); 27 | 28 | return $this; 29 | } 30 | 31 | /** 32 | * Scroll the page in the x-axis by the amount specified. 33 | * 34 | * @param $amount Positive values go down the page, negative values go up the page 35 | * 36 | * @return $this 37 | */ 38 | protected function scroll($amount) 39 | { 40 | $this->wd->executeScript("window.scrollBy(0, {$amount})"); 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * "Select" a drop-down field. 47 | * 48 | * @param $value 49 | * @param $element 50 | * 51 | * @return $this 52 | */ 53 | protected function select($value, $element) 54 | { 55 | $this->wd->findElement(WebDriverBy::cssSelector($element))->sendKeys($value); 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Assert that we see text within the specified tag 62 | * Defaults to the body tag. 63 | * 64 | * @param $text 65 | * @param string $tag 66 | * 67 | * @return $this 68 | */ 69 | protected function see($text, $tag = 'body') 70 | { 71 | $this->assertContains($text, $this->getTextByTag($tag)); 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * User should not be able to see element. 78 | * 79 | * @param $text 80 | * @param string $tag 81 | */ 82 | protected function notSee($text, $tag = 'body') 83 | { 84 | $this->assertNotContains($text, $this->getTextByTag($tag)); 85 | } 86 | 87 | /** 88 | * User should not be able to see element. 89 | * 90 | * @param string $text 91 | * @param string $tag 92 | */ 93 | protected function dontSee($text, $tag = 'body') 94 | { 95 | $this->assertNotContains($text, $this->getTextByTag($tag)); 96 | } 97 | 98 | /** 99 | * Assert the page is at the path that you specified. 100 | * 101 | * @param $path 102 | * 103 | * @return $this 104 | */ 105 | protected function seePageIs($path) 106 | { 107 | $this->assertEquals($this->wd->getCurrentURL(), $this->baseUrl.$path); 108 | 109 | return $this; 110 | } 111 | 112 | public function getTextByTag($tag) 113 | { 114 | return $this->wd->findElement(WebDriverBy::tagName($tag))->getText(); 115 | } 116 | 117 | /** 118 | * Type a value into a form input by that input name. 119 | * Note: Type is an alias of typeBySelectorType. 120 | * 121 | * @param $value 122 | * @param $name 123 | * @param bool $clear Whether or not to clear the input first on say an edit form 124 | * 125 | * @return $this 126 | */ 127 | public function type($value, $name, $clear = false) 128 | { 129 | $element = $this->findElement($name); 130 | 131 | if ($clear) { 132 | $element->clear(); 133 | } 134 | 135 | $element->sendKeys($value); 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * Abstraction for typing into a field with a specific selector type. 142 | * 143 | * @param $type - one of 'Name', 'Id', 'CssSelector' 144 | * @param $value - value to enter into form element 145 | * @param $name - value to use for the selector $type 146 | * @param bool $clear - Whether or not to clear the input first on say an edit form 147 | * 148 | * @throws CannotFindElement 149 | * 150 | * @return $this 151 | */ 152 | private function typeBySelectorType($type, $value, $name, $clear = false) 153 | { 154 | $element = $this->wd->findElement(WebDriverBy::{$type}($name)); 155 | 156 | if ($clear) { 157 | $element->clear(); 158 | } 159 | 160 | $element->sendKeys($value); 161 | 162 | return $this; 163 | } 164 | 165 | /** 166 | * Type a value into a form input by that inputs name. 167 | * 168 | * @param $name 169 | * @param $value 170 | * @param bool $clear Whether or not to clear the input first on say an edit form 171 | * 172 | * @return $this 173 | */ 174 | protected function typeByName($name, $value, $clear = false) 175 | { 176 | return $this->typeBySelectorType('name', $value, $name, $clear); 177 | } 178 | 179 | /** 180 | * Type a value into a form input by that inputs id. 181 | * 182 | * @param $value 183 | * @param $name 184 | * @param bool $clear Whether or not to clear the input first on say an edit form 185 | * 186 | * @return $this 187 | */ 188 | protected function typeById($value, $name, $clear = false) 189 | { 190 | return $this->typeBySelectorType('id', $value, $name, $clear); 191 | } 192 | 193 | /** 194 | * Type a value into a form input by that inputs id. 195 | * 196 | * @param $value 197 | * @param $name 198 | * @param bool $clear Whether or not to clear the input first on say an edit form 199 | * 200 | * @return $this 201 | */ 202 | protected function typeByCssSelector($value, $name, $clear = false) 203 | { 204 | return $this->typeBySelectorType('cssSelector', $value, $name, $clear); 205 | } 206 | 207 | /** 208 | * Function to type information as an array 209 | * The key of the array specifies the input name. 210 | * 211 | * @param $information 212 | * @param $clear 213 | * 214 | * @return $this 215 | */ 216 | protected function typeInformation($information, $clear = false) 217 | { 218 | foreach ($information as $element => $item) { 219 | $this->type($item, $element, $clear); 220 | } 221 | 222 | return $this; 223 | } 224 | 225 | protected function submitForm($inputs, $selector, $clear = false) 226 | { 227 | $form = $this->wd->findElement(WebDriverBy::cssSelector($selector)); 228 | $this->typeInformation($inputs, $clear); 229 | $form->submit(); 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * Press a button on the page that contains text. 236 | * 237 | * @param $text 238 | * 239 | * @return $this 240 | */ 241 | protected function press($text) 242 | { 243 | $this->wd->findElement(WebDriverBy::xpath("//button[contains(., '{$text}')]"))->click(); 244 | 245 | return $this; 246 | } 247 | 248 | /** 249 | * Click an element based on text passed in, or pass an Id or Name to find the element by. 250 | * 251 | * @param $textOrId 252 | * 253 | * @throws CannotClickElement Throws when the element cannot be clicked 254 | * 255 | * @return $this 256 | */ 257 | protected function click($textOrId) 258 | { 259 | $element = $this->wd->findElement(WebDriverBy::xpath("//a[contains(., '{$textOrId}')]")); 260 | 261 | try { 262 | $element->click(); 263 | } catch (\Exception $e) { 264 | throw new CannotClickElement('Cannot click the element with the text: '.$textOrId); 265 | } 266 | 267 | return $this; 268 | } 269 | 270 | /** 271 | * Will attempt to find an element by different patterns 272 | * If xpath is provided, will attempt to find by that first. 273 | * 274 | * @param null $name 275 | * 276 | * @throws CannotFindElement 277 | * 278 | * @return \Facebook\WebDriver\Remote\RemoteWebElement 279 | */ 280 | protected function findElement($name) 281 | { 282 | try { 283 | return $this->wd->findElement(WebDriverBy::id($name)); 284 | } catch (\Exception $e) { 285 | } 286 | 287 | try { 288 | return $this->wd->findElement(WebDriverBy::name($name)); 289 | } catch (\Exception $e) { 290 | } 291 | 292 | try { 293 | return $this->wd->findElement(WebDriverBy::cssSelector($name)); 294 | } catch (\Exception $e) { 295 | } 296 | 297 | try { 298 | return $this->wd->findElement(WebDriverBy::xpath($name)); 299 | } catch (\Exception $e) { 300 | } 301 | 302 | throw new CannotFindElement('Cannot find element: '.$value.' isn\'t visible on the page'); 303 | } 304 | 305 | /** 306 | * Force selenium to wait. 307 | * 308 | * @param int|float $seconds The number of seconds or partial seconds to wait 309 | * 310 | * @return $this 311 | */ 312 | protected function wait($seconds = 1) 313 | { 314 | usleep($seconds * 1000000); 315 | 316 | return $this; 317 | } 318 | 319 | /** 320 | * Alias for wait. 321 | * 322 | * @param int $seconds 323 | * 324 | * @return TestCase 325 | */ 326 | protected function hold($seconds = 1) 327 | { 328 | return $this->wait($seconds); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/Services/ManageWindow.php: -------------------------------------------------------------------------------- 1 | wd->manage()->window()->setPosition(new WebDriverPoint($width, $height)); 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * Set the current window's width. 32 | * 33 | * @param $width 34 | * 35 | * @return \Facebook\WebDriver\WebDriverWindow 36 | */ 37 | public function setWidth($width) 38 | { 39 | return $this->wd->manage() 40 | ->window() 41 | ->setPosition(new WebDriverPoint($width, self::BROWSER_HEIGHT)); 42 | } 43 | 44 | /** 45 | * Set the current window's height. 46 | * 47 | * @param int $height 48 | * 49 | * @return $this 50 | */ 51 | public function setHeight($height) 52 | { 53 | return $this->changeWindowSize(self::BROWSER_WIDTH, $height); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Services/WaitForElement.php: -------------------------------------------------------------------------------- 1 | waitForTypes)) { 24 | throw new \Exception('Invalid wait for element type to wait for on the page'); 25 | } 26 | 27 | $webdriver = $this; 28 | $this->waitUntil(function () use ($type, $value, $webdriver) { 29 | $function = 'by'.$type; 30 | 31 | try { 32 | $webdriver->$function($value); 33 | 34 | return true; 35 | } catch (\Exception $e) { 36 | return; // haven't found the element yet 37 | } 38 | }, $timeout); 39 | } 40 | 41 | /** 42 | * Helper method to wait for an element with the specified class. 43 | * 44 | * @param $class 45 | * @param int $timeout 46 | * 47 | * @throws CannotFindElement 48 | * 49 | * @return $this 50 | */ 51 | protected function waitForElementsWithClass($class, $timeout = 2000) 52 | { 53 | try { 54 | $this->waitForElement('ClassName', $class, $timeout); 55 | } catch (\Exception $e) { 56 | throw new CannotFindElement("Can't find an element with the class name of " 57 | .$class.' within the time period of '.$timeout.' miliseconds'); 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Helper method to wait for an element with the specified id. 65 | * 66 | * @param $id 67 | * @param int $timeout 68 | * 69 | * @throws CannotFindElement 70 | * 71 | * @return $this 72 | */ 73 | protected function waitForElementWithId($id, $timeout = 2000) 74 | { 75 | try { 76 | $this->waitForElement('Id', $id, $timeout); 77 | } catch (\Exception $e) { 78 | throw new CannotFindElement("Can't find an element with an ID of " 79 | .$id.' within the time period of '.$timeout.' miliseconds'); 80 | } 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Helper method to wait for an element with the specified xpath. 87 | * 88 | * @param $xpath 89 | * @param int $timeout 90 | * 91 | * @throws CannotFindElement 92 | * 93 | * @return $this 94 | */ 95 | protected function waitForElementWithXPath($xpath, $timeout = 2000) 96 | { 97 | try { 98 | $this->waitForElement('XPath', $xpath, $timeout); 99 | } catch (\Exception $e) { 100 | throw new CannotFindElement("Can't find an element with an XPath of " 101 | .$xpath.' within the time period of '.$timeout.' miliseconds'); 102 | } 103 | 104 | return $this; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Services/WorkWithDatabase.php: -------------------------------------------------------------------------------- 1 | app)) { 19 | return; // don't run when there is no application 20 | } 21 | 22 | $database = $this->app->make('db'); 23 | 24 | $connection = $connection ?: $database->getDefaultConnection(); 25 | 26 | $count = $database->connection($connection)->table($table)->where($data)->count(); 27 | 28 | $this->assertGreaterThan(0, $count, sprintf( 29 | 'Unable to find row in database table [%s] that matched attributes [%s].', $table, json_encode($data) 30 | )); 31 | 32 | return $this; 33 | } 34 | 35 | /** 36 | * Assert that a given where condition does not exist in the database. 37 | * 38 | * @param string $table 39 | * @param array $data 40 | * @param string $connection 41 | * 42 | * @return $this 43 | */ 44 | protected function missingFromDatabase($table, array $data, $connection = null) 45 | { 46 | return $this->notSeeInDatabase($table, $data, $connection); 47 | } 48 | 49 | /** 50 | * Assert that a given where condition does not exist in the database. 51 | * 52 | * @param string $table 53 | * @param array $data 54 | * @param string $connection 55 | * 56 | * @return $this 57 | */ 58 | protected function dontSeeInDatabase($table, array $data, $connection = null) 59 | { 60 | return $this->notSeeInDatabase($table, $data, $connection); 61 | } 62 | 63 | /** 64 | * Assert that a given where condition does not exist in the database. 65 | * 66 | * @param string $table 67 | * @param array $data 68 | * @param string $connection 69 | * 70 | * @return $this 71 | */ 72 | protected function notSeeInDatabase($table, array $data, $connection = null) 73 | { 74 | if (is_null($this->app)) { 75 | return; // don't run when there is no application 76 | } 77 | 78 | $database = $this->app->make('db'); 79 | 80 | $connection = $connection ?: $database->getDefaultConnection(); 81 | 82 | $count = $database->connection($connection)->table($table)->where($data)->count(); 83 | 84 | $this->assertEquals(0, $count, sprintf( 85 | 'Found unexpected records in database table [%s] that matched attributes [%s].', $table, json_encode($data) 86 | )); 87 | 88 | return $this; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/TestingMiddleware.php: -------------------------------------------------------------------------------- 1 | 'testing']); 14 | } 15 | 16 | return $next($request); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Traits/WebDriverUtilsTrait.php: -------------------------------------------------------------------------------- 1 | 'mac', 14 | 'winnt' => 'win', 15 | 'win32' => 'win', 16 | 'windows' => 'win', 17 | 'linux' => 'linux', 18 | ]; 19 | 20 | /** 21 | * Web driver. 22 | * 23 | * @var string 24 | */ 25 | protected $driver; 26 | 27 | /** 28 | * User current operating system. 29 | * 30 | * @var string 31 | */ 32 | protected $userOS; 33 | 34 | /** 35 | * Resource file of web driver (raw zip file). 36 | * 37 | * @var string 38 | */ 39 | protected $resource; 40 | 41 | /** 42 | * Configuration of web drivers. 43 | * 44 | * @var array 45 | */ 46 | protected $config; 47 | 48 | /** 49 | * @param string|null $key 50 | * 51 | * @return string 52 | */ 53 | abstract public function argument($key = null); 54 | 55 | /** 56 | * Get driver. 57 | * 58 | * @return string 59 | */ 60 | protected function getDriver() 61 | { 62 | return $this->driver ?: $this->driver = $this->argument('driver'); 63 | } 64 | 65 | /** 66 | * Get user operating system. 67 | * 68 | * @return mixed 69 | */ 70 | public function getUserOS() 71 | { 72 | return $this->userOS ?: $this->userOS = @$this->os[mb_strtolower(PHP_OS)]; 73 | } 74 | 75 | /** 76 | * Loading drivers specific download path and version. 77 | * 78 | * @return mixed 79 | */ 80 | public function getConfig() 81 | { 82 | return $this->config ?: $this->setConfig()->getConfig(); 83 | } 84 | 85 | /** 86 | * Set the configuration for web drivers. 87 | * 88 | * @throws \ErrorException 89 | * 90 | * @return $this 91 | */ 92 | public function setConfig() 93 | { 94 | $this->config = (require static::prependPackagePath('bootstrap/config.php'))['web-drivers']; 95 | 96 | $this->config = @$this->config[$this->getDriver()][$this->getUserOS()]; 97 | 98 | if (!$this->config) { 99 | throw new \ErrorException("Currently {$this->getDriver()} not supported."); 100 | } 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * Get web driver file name with extension. 107 | * 108 | * @return string 109 | */ 110 | public function getFileName() 111 | { 112 | return "{$this->getUserOS()}-{$this->getDriver()}{$this->getFileExtension()}"; 113 | } 114 | 115 | /** 116 | * Get web driver file extension. 117 | * 118 | * @return string 119 | */ 120 | public function getFileExtension() 121 | { 122 | return $this->getUserOS() == 'win' ? '.exe' : ''; 123 | } 124 | 125 | /** 126 | * Get the real path with package path prepended. 127 | * 128 | * @param string $suffix suffix the file needed 129 | * @param bool $assume Possible directory can be created. 130 | * 131 | * @throws \Exception 132 | * 133 | * @return mixed 134 | */ 135 | public static function prependPackagePath($suffix, $assume = false) 136 | { 137 | $path = __DIR__."/../../$suffix"; 138 | 139 | if (!$assume and !$path) { 140 | throw new \Exception("$path path does not exists."); 141 | } 142 | 143 | return $assume ? $path : realpath($path); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /steward.yml: -------------------------------------------------------------------------------- 1 | logs_dir: storage/tests/ 2 | -------------------------------------------------------------------------------- /storage/tests/.keep: -------------------------------------------------------------------------------- 1 | Keep this file. 2 | -------------------------------------------------------------------------------- /storage/views/.keep: -------------------------------------------------------------------------------- 1 | Keep this file. -------------------------------------------------------------------------------- /stubs/public/index.php: -------------------------------------------------------------------------------- 1 | make(Illuminate\Contracts\Http\Kernel::class); 24 | 25 | $response = $kernel->handle( 26 | $request = Illuminate\Http\Request::capture() 27 | ); 28 | 29 | $response->send(); 30 | -------------------------------------------------------------------------------- /stubs/test.stub: -------------------------------------------------------------------------------- 1 | visit('/'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /stubs/views/about.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Selenium Test HTML File 5 | 6 | 7 | 13 | 14 | 15 | @include('partial.nav') 16 | 17 | 18 |
19 | 20 |
21 |
22 |

About this project

23 |
24 |
25 |
26 |
27 |
28 | 29 |

StyleCI 30 | Latest Stable Version 31 | Total Downloads 32 | Latest Unstable Version 33 | License 34 | composer.lock

35 | 36 |

37 | 38 |

Requirements:

39 | 40 |
    41 |
  1. Java should be installed on local machine.
  2. 42 |
  3. You should have at least basic understanding of phpunit.
  4. 43 |
44 | 45 |

Installation guide:

46 | 47 |

First get the package on your laravel instance

48 | 49 |
composer require modelizer/selenium "~0.2"
50 | 51 |

Set configuration to your .env file.

52 | 53 |
APP_URL="http://example.dev/"   # If not set in .env file then http://localhost will be use as default
 54 | SELENIUM_WIDTH=1024 # If not set in the .env file, the default window width will be used
 55 | SELENIUM_HEIGHT=768 # If not set in the .env file, then the default window height will be used
56 | 57 |

Register Service provider in app.php

58 | 59 |
Modelizer\Selenium\SeleniumServiceProvider::class 
60 | 61 |

Start Selenium Server

62 | 63 |
php artisan selenium:start
64 | 65 |

Start Testing

66 | 67 |
    68 |
  1. Create a dummy SeleniumExampleTest.php file in tests directory.
  2. 69 |
  3. Add this code to SeleniumExampleTest.php file and run phpunit vendor/bin/phpunit tests/SeleniumExampleTest.php
  4. 70 |
71 | 72 |
<?php
 73 | 
 74 | use Modelizer\Selenium\SeleniumTestCase;
 75 | 
 76 | class SeleniumExampleTest extends SeleniumTestCase
 77 | {
 78 |     /**
 79 |      * A basic functional test example.
 80 |      *
 81 |      * @return void
 82 |      */
 83 |     public function testBasicExample()
 84 |     {
 85 |         // This is a sample code you can change as per your current scenario
 86 |         $this->visit('/')
 87 |              ->see('Laravel')
 88 |              ->hold(3);
 89 |     }
 90 | 
 91 |     /**
 92 |      * A basic submission test example.
 93 |      *
 94 |      * @return void
 95 |      */
 96 |     public function testLoginFormExample()
 97 |     {
 98 |         $loginInput = [
 99 |             'username' => 'dummy-name',
100 |             'password' => 'dummy-password'
101 |         ];
102 | 
103 |         // Login form test case scenario
104 |         $this->visit('/login')
105 |              ->submitForm($loginInput, '#login-form')
106 |              ->see('Welcome');  // Expected Result
107 |     }
108 | }
109 | 110 |

Api Added in 0.2 release:

111 | 112 |
    113 |
  1. scroll, notSee, seePageIs, type, typeInformation, press, click, findElement and much more.
  2. 114 |
  3. To know more about this API you can checkout Integrated Package API
  4. 115 |
  5. Database related APIs is also available such as seeInDatabae and missingFromDatabase, dontSeeInDatabase
  6. 116 |
  7. Full API documentation will be available soon.
  8. 117 |
118 | 119 |

Notes:

120 | 121 |
    122 |
  1. Mac and windows support is available.
  2. 123 |
  3. Currently only support chrome browser.
  4. 124 |
  5. Selenium 2.53.1 and ChromeDriver 2.24 is been used.
  6. 125 |
  7. Feel free to contribute or create an issue.
  8. 126 |
  9. The user will not be able to swap between PHPUnit and Selenium who are below Laravel 5.3.
  10. 127 |
128 | 129 |

Roadmap:

130 | 131 |
    132 |
  1. Firefox support needs to be added.
  2. 133 |
  3. Windows and Linux support needs to be added.
  4. 134 |
  5. Drivers file and selenium standalone package need to be compressed.
  6. 135 |
  7. API Docs need to be created.
  8. 136 |
137 | 138 |

Summary:

139 | 140 |

Many APIs such as see, wait, submitForm etc are been implemented in Laravel 5.3, and the whole goal of this package is to make it easier for the user to swap testing type anytime. 141 | Eg: If a user wants to test by selenium then he only need to extend Modelizer\Selenium\SeleniumTestCase in his test case or if he wants to do PHPUnit testing then he will be able to do it by extending TestCase which Laravel 5.3 provide by default. This will help the user to test a case in many different testing types without doing any changes with API.

142 |
143 |

Developed by:

144 |
    145 |
  • Mohammed Mudasir (https://github.com/Modelizer)
  • 146 |
  • John Hoopes (https://github.com/jhoopes)
  • 147 |
148 |
149 |
150 | 151 | 152 |
153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /stubs/views/contact.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Selenium Test HTML File 5 | 6 | 7 | 13 | 14 | 15 | @include('partial.nav') 16 | 17 | 18 |
19 | 20 |
21 |
22 |

Contact Us

23 |
View Larger Map 24 | 25 |
26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 | 38 | 39 |
40 |
41 |
42 |
43 |

Developed by:

44 |
    45 |
  • Mohammed Mudasir (https://github.com/Modelizer)
  • 46 |
  • John Hoopes (https://github.com/jhoopes)
  • 47 |
48 |
49 |
50 | 51 | 52 |
53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /stubs/views/form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Selenium Test HTML File 5 | 6 | 7 | 13 | 14 | 15 | @include('partial.nav') 16 | 17 | 18 |
19 |
20 |
21 |

Form Test

22 |
23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 | 47 |
48 |
49 |
50 | 51 |
52 | 53 |
54 |
55 |
56 | 57 |
58 | 59 |
60 |
61 |
62 | 63 |
64 | 69 |
70 |
71 | 76 |
77 |
78 | 83 |
84 |
85 |
86 | 87 |
88 | 89 |
90 |
91 |
92 | 93 |
94 | 95 |
96 |
97 |
98 | 99 |
100 | 103 |
104 |
105 | 108 |
109 |
110 |
111 |
112 | 115 |
116 |
117 |
118 |
119 | 122 |
123 |
124 |
125 |
126 |
127 | 128 | 129 |
130 |
131 |
132 |
133 |
134 |

Developed by:

135 |
    136 |
  • Mohammed Mudassir (https://github.com/Modelizer)
  • 137 |
  • John Hoopes (https://github.com/jhoopes)
  • 138 |
139 |
140 |
141 | 142 |
143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /stubs/views/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Selenium Test HTML File 5 | 6 | 7 | 13 | 14 | 15 | @include('partial.nav') 16 | 17 | 18 |
19 | 20 |
21 |
22 |

Laravel Selenium Test Helper

23 |
24 |
25 |
26 |
27 |

28 | Affogato viral fingerstache blog schlitz, semiotics green juice. 8-bit squid photo booth tattooed tumeric af actually, pabst literally gentrify bushwick kitsch. Post-ironic food truck etsy man bun pitchfork PBR&B. Helvetica neutra 8-bit, succulents squid wolf tofu hexagon meh wayfarers. IPhone try-hard occupy lyft XOXO. Whatever bicycle rights waistcoat vaporware, microdosing vice chambray snackwave mustache man braid viral seitan. Migas post-ironic banh mi cred, twee seitan biodiesel tousled whatever ugh. 29 |

30 |

31 | Bespoke heirloom man bun, yuccie tumblr selfies skateboard taxidermy viral mlkshk cardigan tote bag. Chartreuse tousled umami migas, hot chicken tacos meh shoreditch. Art party blue bottle actually, subway tile quinoa raclette four dollar toast neutra banjo sriracha. Tbh neutra tote bag narwhal. Locavore pug fashion axe try-hard, small batch iPhone crucifix gochujang vape before they sold out wayfarers hammock truffaut. Wolf wayfarers vinyl freegan biodiesel. Tumblr kinfolk prism microdosing, sustainable fingerstache man bun. 32 |

33 |

34 | Pitchfork cray pabst, fingerstache flannel skateboard hashtag cold-pressed chicharrones bitters mustache vape next level. Kitsch try-hard waistcoat meditation XOXO cray. Humblebrag kogi wayfarers banh mi, copper mug beard ennui hammock. Kickstarter sriracha meditation crucifix man braid. Organic viral scenester, pickled knausgaard fam mustache brunch listicle green juice echo park. Taxidermy marfa put a bird on it, brooklyn letterpress pour-over pitchfork master cleanse man bun 3 wolf moon yuccie mustache pop-up flexitarian butcher. Tumeric vegan trust fund, normcore slow-carb hexagon messenger bag fap freegan lo-fi vaporware XOXO. 35 |

36 |

37 | Salvia small batch cray etsy pickled, aesthetic tofu iPhone deep v coloring book slow-carb distillery. Twee cred distillery semiotics cold-pressed, church-key typewriter. Food truck brooklyn small batch banjo ramps, keffiyeh tumblr migas cold-pressed iceland aesthetic letterpress literally. Food truck church-key fap vaporware paleo 8-bit tofu. Selvage bushwick truffaut post-ironic fingerstache crucifix, direct trade tote bag. Mixtape hashtag coloring book, raw denim VHS asymmetrical cred. Woke sartorial ethical, wolf hexagon sriracha meh drinking vinegar literally. 38 |

39 |

40 | Chicharrones aesthetic kogi, crucifix single-origin coffee keytar YOLO drinking vinegar enamel pin PBR&B franzen copper mug intelligentsia. Keffiyeh crucifix franzen hoodie biodiesel, disrupt pitchfork pork belly organic selvage skateboard glossier trust fund heirloom activated charcoal. Cold-pressed viral af banjo. Swag YOLO 90's sartorial. Ugh migas pug tilde, jean shorts vegan snackwave pickled XOXO hammock. Fingerstache viral paleo, live-edge YOLO mumblecore readymade. Wayfarers normcore bicycle rights, chillwave hammock leggings cred mixtape shabby chic microdosing next level. 41 |

42 |

43 | Portland raw denim tattooed bicycle rights. Occupy mustache vice, listicle umami pork belly banjo chartreuse before they sold out banh mi. Chillwave chartreuse ethical, salvia truffaut man braid slow-carb gluten-free selfies hexagon blue bottle coloring book chambray small batch. Schlitz messenger bag brooklyn vegan farm-to-table gentrify. Locavore pickled wayfarers sartorial. XOXO microdosing artisan, mlkshk swag fingerstache seitan leggings lomo. Heirloom keytar schlitz, humblebrag vape neutra skateboard fap stumptown put a bird on it you probably haven't heard of them plaid. 44 |

45 |

Developed by:

46 |
    47 |
  • Mohammed Mudasir (https://github.com/Modelizer)
  • 48 |
  • John Hoopes (https://github.com/jhoopes)
  • 49 |
50 |
51 |
52 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /stubs/views/partial/nav.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /stubs/views/success.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Selenium Test HTML File 5 | 6 | 7 | 13 | 14 | 15 | @include('partial.nav') 16 | 17 | 18 |
19 |
20 |
21 |

Form Test

22 |
23 |
24 |

Form successfully submitted

25 |
26 |
27 |

Developed by:

28 |
    29 |
  • Mohammed Mudassir (https://github.com/Modelizer)
  • 30 |
  • John Hoopes (https://github.com/jhoopes)
  • 31 |
32 |
33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /testing.env: -------------------------------------------------------------------------------- 1 | APP_URL=http://127.0.0.1:8000 2 | APP_NAME="Laravel Selenium" 3 | APP_ENV=local 4 | APP_KEY=base64:VoAYEUb5LQN93a9PRxpM+4hpwNq/giYvJVUg7gDx4mA= 5 | APP_DEBUG=true 6 | CACHE_DRIVER=array 7 | SESSION_DRIVER=array 8 | QUEUE_DRIVER=sync 9 | -------------------------------------------------------------------------------- /tests/feature/Console/GetSeleniumServerTest.php: -------------------------------------------------------------------------------- 1 | visit('/'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/feature/FormTest.php: -------------------------------------------------------------------------------- 1 | visit() 13 | ->click('Form') 14 | ->see('Form Test') 15 | ->type('Mohammed', 'firstName') 16 | ->type('Mudassir', 'lastName') 17 | ->typeById('hello@mudasir.me', 'inputEmail'); 18 | } 19 | 20 | /** @test */ 21 | public function it_should_type_information() 22 | { 23 | $formInfo = [ 24 | 'firstName' => 'Mohammed', 25 | 'lastName' => 'Mudassir', 26 | 'inputEmail' => 'hello@mudasir.me', 27 | ]; 28 | 29 | $this->visit() 30 | ->click('Form') 31 | ->see('Form Test') 32 | ->typeInformation($formInfo); 33 | } 34 | 35 | /** @test */ 36 | public function it_should_type_email_by_name() 37 | { 38 | $this->visit() 39 | ->click('Form') 40 | ->type('alice@foo.com', 'inputEmail-name'); 41 | } 42 | 43 | /** @test */ 44 | public function it_should_type_email_by_id() 45 | { 46 | $this->visit() 47 | ->click('Form') 48 | ->typeById('bob@bar.com', 'inputEmail'); 49 | } 50 | 51 | /** @test */ 52 | public function it_should_type_by_css_selector() 53 | { 54 | $this->visit() 55 | ->click('Form') 56 | ->select(1, '[name=dateDOB]') 57 | ->typeByCssSelector('bob@bar.com', '#inputEmail'); 58 | } 59 | 60 | /** @test */ 61 | public function it_should_type_information_and_press_a_button() 62 | { 63 | $formInfo = [ 64 | 'firstName' => 'Mohammed', 65 | 'lastName' => 'Mudassir', 66 | 'inputEmail-name' => 'hello@mudasir.me', 67 | ]; 68 | 69 | $this->visit() 70 | ->click('Form') 71 | ->see('Form Test') 72 | ->typeInformation($formInfo) 73 | ->press('Submit') 74 | ->see('Form successfully submitted'); 75 | } 76 | 77 | /** @test */ 78 | public function it_should_submit_form() 79 | { 80 | $formInfo = [ 81 | 'firstName' => 'Mohammed', 82 | 'lastName' => 'Mudassir', 83 | 'inputEmail-name' => 'hello@mudasir.me', 84 | ]; 85 | 86 | $this->visit() 87 | ->click('Form') 88 | ->see('Form Test') 89 | ->submitForm($formInfo, '#newAccount') 90 | ->see('Form successfully submitted'); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/feature/InteractionTest.php: -------------------------------------------------------------------------------- 1 | visit(); 13 | } 14 | 15 | /** @test */ 16 | public function it_should_see_page_url() 17 | { 18 | $this->visit('/about') 19 | ->seePageIs('/about'); 20 | } 21 | 22 | /** @test */ 23 | public function it_should_see_text_on_page() 24 | { 25 | $this->visit() 26 | ->see('Laravel Selenium Test Helper'); 27 | } 28 | 29 | /** @test */ 30 | public function check_text_not_exists_on_page() 31 | { 32 | $this->visit() 33 | ->notSee('Bootstrap'); 34 | } 35 | 36 | /** @test */ 37 | public function it_should_click_link() 38 | { 39 | $this->visit() 40 | ->click('Contact Us') 41 | ->see('Contact Us'); 42 | } 43 | 44 | /** @test */ 45 | public function it_should_scroll() 46 | { 47 | $this->visit() 48 | ->click('About') 49 | ->see('About this project') 50 | ->scroll(500) 51 | ->see('Notes'); 52 | } 53 | } 54 | --------------------------------------------------------------------------------