JZK
JZK

Reputation: 577

Set up tests with PHPUnit and Selenium

Can you help me with setting up my test enviroment please. I'm running on Ubuntu, have selenium web server installed (and running), and trought PHPUnit I'm executing my tests. Most propably I've got stucked on some small error, but I have no I idea how to fix it now.

my code is simple

class WebTest extends PHPUnit_Extensions_Selenium2TestCase 
{
protected function setUp()

{
    $this->setBrowser('firefox');
    $this->setBrowserUrl('http://www.google.com/');
}

public function testTitle()
{
    $this->url('http://www.google.com/');
    $this->assertEquals('google', $this->title());
}

but getting this error

PHP Fatal error: Class 'PHPUnit_Extensions_Selenium2TestCase' not found in /home/jozef/vendor/phpunit/phpunit-selenium/WebTest.php on line 4

Selenium I have installed

Can you please help me move on ? Thank you :)

Upvotes: 3

Views: 2382

Answers (2)

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92743

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 // Because seleniumIDE tests are written in old format (selenium 1) so we use this adapter
    {
        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);
        }
    
        /**
         * Make screenshot.
         * @return
         */
        public function screenshot()
        {
            $this->listener->addError($this, new Exception, null); // this function create screenshot
        }
    
        /**
         * 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)
         * Function execute javascript code and is used in selenium IDE tests e.g. in function 'storeEval'.
         * @param  string $javascriptCode is JS Code e.g. "storedVars['registerurl'].match(/[^\\/]+$/)"
         * @param  [type] $args           associative array key-value which shoud be set in storedVars. e.g.
         *                                $args=['registerurl'=>'http://example.com']
         * @return string or array        if JS result is string/number then return it
         *                   							if JS result is array then return array.
         */
        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, '[email protected]', '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 = '[email protected]';
          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 :)

Upvotes: 3

Eudes Costa
Eudes Costa

Reputation: 31

Did you recently update your phpunit?

The latest version of phpunit are not compiled with this php binding anymore, just faced the same problem.

Can you test using the phpunit-4.7.0 version?

/usr/bin/wget https://phar.phpunit.de/phpunit-4.7.0.phar -O /vagrant/tools/phpunit.phar && chmod +x /vagrant/tools/phpunit.phar && sudo mv /vagrant/tools/phpunit.phar /usr/local/bin/phpunit

Searched my bash history and pasted up there, just correct the path for your environment.

The line above should update your phpunit for the 4.7.0 version, this is a version that the phar was compiled with the PHPUnit_Extensions_Selenium2TestCase bindings.

This should work, just make sure this phpunit version downgrade won't cause any side effects for you.

Upvotes: 2

Related Questions