简体   繁体   中英

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 :)

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 :)

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM