Here is instruction how to run Selenium IDE test recorded on Firefox on phpUnit, MacOS, laravel 5.2, firefox. I also show how to set up screenshot here (i also set up Laravel to heve access to DB to clean it up after test ends):
In your test-s directory, create selenium
directory. And create files:
SeleniumClearTestCase.php
class SeleniumClearTestCase extends MigrationToSelenium2 // Poniewaz testy seleniumIDE są zapisane w starym formacie (selenium 1) to urzywamy tej przejsciowki.
{
protected $baseUrl = 'http://yourservice.dev';
protected function setUp()
{
$screenshots_dir = __DIR__.'/screenshots';
if (! file_exists($screenshots_dir)) {
mkdir($screenshots_dir, 0777, true);
}
$this->listener = new PHPUnit_Extensions_Selenium2TestCase_ScreenshotListener($screenshots_dir);
$this->setBrowser('firefox');
$this->setBrowserUrl($this->baseUrl);
$this->createApplication(); // bootstrap laravel app
}
public function onNotSuccessfulTest($e)
{
$this->listener->addError($this, $e, null);
parent::onNotSuccessfulTest($e);
}
/**
* Wykonaj screenshot w danym mommencie.
* @return
*/
public function screenshot()
{
$this->listener->addError($this, new Exception, null); // ta funkcja troche myli nazwą, ale wykona ona tylko screenshota nic ponadto
}
/**
* Creates the application.
*
* @return \Illuminate\Foundation\Application
*/
public function createApplication()
{
$app = require __DIR__.'/../../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
return $app;
}
}
Next file: MigrationToSelenium2.php (from github but I add some moficiations):
<?php
/*
* Copyright 2013 Roman Nix
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Implements adapter for migration from PHPUnit_Extensions_SeleniumTestCase
* to PHPUnit_Extensions_Selenium2TestCase.
*
* If user's TestCase class is implemented with old format (with commands
* like open, type, waitForPageToLoad), it should extend MigrationToSelenium2
* for Selenium 2 WebDriver support.
*/
abstract class MigrationToSelenium2 extends LaravelTestCase // MY modification - extends diffrent class. If you don't want use laravel, extends this class by PHPUnit_Extensions_Selenium2TestCase
{
public function open($url)
{
$this->url($url);
}
public function type($selector, $value)
{
$input = $this->byQuery($selector);
$input->value($value);
}
protected function byQuery($selector)
{
if (preg_match('/^\/\/(.+)/', $selector)) {
/* "//a[contains(@href, '?logout')]" */
return $this->byXPath($selector);
} elseif (preg_match('/^([a-z]+)=(.+)/', $selector, $match)) {
/* "id=login_name" */
switch ($match[1]) {
case 'id':
return $this->byId($match[2]);
break;
case 'name':
return $this->byName($match[2]);
break;
case 'link':
return $this->byPartialLinkText($match[2]);
break;
case 'xpath':
return $this->byXPath($match[2]);
break;
case 'css':
$cssSelector = str_replace('..', '.', $match[2]);
return $this->byCssSelector($cssSelector);
break;
}
}
throw new Exception("Unknown selector '$selector'");
}
protected function waitForPageToLoad($timeout)
{
$this->timeouts()->implicitWait((int) $timeout); // MY modification - cast to 'int'
}
public function click($selector)
{
$input = $this->byQuery($selector);
$input->click();
}
public function select($selectSelector, $optionSelector)
{
$selectElement = parent::select($this->byQuery($selectSelector));
if (preg_match('/label=(.+)/', $optionSelector, $match)) {
$selectElement->selectOptionByLabel($match[1]);
} elseif (preg_match('/value=(.+)/', $optionSelector, $match)) {
$selectElement->selectOptionByValue($match[1]);
} else {
throw new Exception("Unknown option selector '$optionSelector'");
}
}
public function isTextPresent($text)
{
if (strpos($this->byCssSelector('body')->text(), $text) !== false) {
return true;
} else {
return false;
}
}
public function isElementPresent($selector)
{
$element = $this->byQuery($selector);
if ($element->name()) {
return true;
} else {
return false;
}
}
public function getText($selector)
{
$element = $this->byQuery($selector);
return $element->text();
}
/** MY MODIFICATION (support for getEval)
* Funkcja wykonuje kod js i jest uzywana w testach selenium IDE np. w funkcji 'storeEval'.
* @param string $javascriptCode Kod w JS np. "storedVars['registerurl'].match(/[^\\/]+$/)"
* @param [type] $args tablica asocjacyjna klucz wartość z wartościami
* jakie mają się znaleźć w zmiennej storedVars. np.
* $args=['registerurl'=>'http://example.com']
* @return string or array jeżeli rezultat JS to string/liczba to zwraca je jak są
* jeżeli rezultat JS to tablica, to zwraca tablicę.
*/
public function getEval($javascriptCode, $args)
{
$sv = 'storedVars=[]; ';
foreach ($args as $key => $val) {
$sv = $sv."storedVars['".$key."']='".$val."'; ";
}
$result = $this->execute(['script' => $sv.' return '.$javascriptCode, 'args' => []]);
return $result;
}
}
Next file: LaravelTestCase.php this is exact copy of Illuminate\Foundation\Testing\TestCase but it not extends PHPUnit_Framework_TestCase, but PHPUnit_Extensions_Selenium2TestCase class.
Last file: in test directory create file testrunner
(which is bash script):
seleniumIsRun=`ps | grep -w selenium.jar | grep -v grep | wc -l`
if (( $seleniumIsRun == 0 )); then # run selenium server if it is not run already
java -jar ./tests/selenium/selenium.jar &
sleep 5s
fi
rm -r ./tests/selenium/screenshots
php artisan db:seed # reset DB using laravel (my laravel seeders clean db at the begining)
vendor/bin/phpunit # run php unit (in laravel it is in this direcotry)
Next step, download newest "Selenium Standalone Server" from http://www.seleniumhq.org/download/ and change it's name and copy it to tests/selenium/selenium.jar.
Next step, if you don't have java
command in console install newerst JDK from http://www.oracle.com/technetwork/java/javase/downloads/index.html
LARAVEL
In composer.json update sections (add: phpunit/phpunit-selenium (github) and our new classes)
"require-dev": {
"fzaninotto/faker": "~1.4",
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "~4.0",
"symfony/css-selector": "2.8.*|3.0.*",
"symfony/dom-crawler": "2.8.*|3.0.*",
"phpunit/phpunit-selenium": "> 1.2"
},
"autoload-dev": {
"classmap": [
"tests/selenium/SeleniumClearTestCase.php",
"tests/selenium/MigrationToSelenium2.php",
"tests/selenium/LaravelTestCase.php",
"tests/TestCase.php"
]
},
Then run
composer update
and
composer dump-autoload
Ok now we have all files to setUp selenium and phpunit. So let's make some tests using plugin Selenium IDE in firefox, we also need to install 'Selenium IDE: PHP Formatters' plugin for save test as phpunit. When we record test, and we check that it works and we save it as phpunit (and we can also save test in native html selenium format .se - to have 'source' of our php test, and be able in future to run it in selenium IDE by hand in futre...) - then we copy it to folder test/selenium/tests. And then we change test by remove setUp part, and also change extension class to SeleniumClearTestCase
. For instance we can create test like this:
<?php
class LoginTest extends SeleniumClearTestCase
{
public function testAdminLogin()
{
self::adminLogin($this);
}
public function testLogout()
{
self::adminLogin($this);
//START POINT: User zalogowany
self::logout($this);
}
public static function adminLogin($t)
{
self::login($t, 'jan.kowalski@gmail.com', 'secret_password');
$t->assertEquals('Jan Kowalski', $t->getText('css=span.hidden-xs'));
}
// @source LoginTest.se
public static function login($t, $login, $pass)
{
$t->open('/');
$t->click("xpath=(//a[contains(text(),'Panel')])[2]");
$t->waitForPageToLoad('30000');
$t->type('name=email', $login);
$t->type('name=password', $pass);
$t->click("//button[@type='submit']");
$t->waitForPageToLoad('30000');
}
// @source LogoutTest.se
public static function logout($t)
{
$t->click('css=span.hidden-xs');
$t->click('link=Wyloguj');
$t->waitForPageToLoad('30000');
$t->assertEquals('PANEL', $t->getText("xpath=(//a[contains(text(),'Panel')])[2]"));
}
}
As you can see I put parts that can be reusable to separate STATIC functions. And below more Complicated test which use that static functions (which also clen up DB):
<?php
use App\Models\Device;
use App\Models\User;
class DeviceRegistrationTest extends SeleniumClearTestCase
{
public function testDeviceRegistration()
{
$email = 'paris@gmail.com';
self::registerDeviceAndClient($this,'Paris','Hilton',$email,'verydifficultpassword');
self::cleanRegisterDeviceAndClient($email);
}
// ------- STATIC elements
public static function registerDeviceAndClient($t,$firstname, $lastname, $email, $pass) {
LoginTest::adminLogin($t);
// START POINT: User zalogowany jako ADMIN
$t->click('link=Urządzenia');
$t->click('link=Rejestracja');
$t->waitForPageToLoad('30000');
$registerurl = $t->getText('css=h4');
$token = $t->getEval("storedVars['registerurl'].match(/[^\\/]+$/)", compact('registerurl'))[0];
$t->screenshot();
// LOG OUT ADMIn
LoginTest::logout($t);
// Otwórz link do urzadzenia
$t->open($registerurl);
$t->click('link=Rejestracja');
$t->waitForPageToLoad('30000');
$t->type('name=email', $email);
$t->screenshot(); // take some photo =)
$t->click('css=button.button-control');
$t->waitForPageToLoad('30000');
// Symuluj klikniecie w link aktywacyjny z emaila
$t->open('http://yourdomain.dev/rejestracja/'.$token);
$t->type('name=firstname', $firstname);
$t->type('name=lastname', $lastname);
$t->type('name=password', $pass);
$t->type('name=password_confirmation', $pass);
$t->screenshot(); // powinno byc widac formularz rejestracyjny nowego klienta
$t->click("//button[@type='submit']");
$t->waitForPageToLoad('30000');
// Asercje
$t->assertEquals($firstname.' '.$lastname, $t->getText('css=span.hidden-xs'));
}
public static function cleanRegisterDeviceAndClient($email) {
$user = User::where('email','=',$email)->first();
$device = Device::where('client_user_id','=',$user->id);
$device->forceDelete();
$user->forceDelete();
}
}
And You run test by
./testrunner
enjoy :)