Yii 1.1: webdriver-test

Extension using Selenium WebDriver for functional tests
22 followers

This extensions allows to run functionaln tests using WebDriver functions from Selenium Server 2.0. WebDriver runs as a plugin in remote browser, so it is much more reliable than standard Selenium test injected through JavaScript.

Extension is using PHP WebDriver Bindings project from http://code.google.com/p/php-webdriver-bindings/

Requirements

Requires PHP WebDriver Bindings (they are packaged in this extension) and Selenium Server 2.0 as tests runtime environment. Should work with Firefox and Chrome browsers (they are supported by Selenium WebDriver)

Usage

Extract archive in your extensions directory. Write functional test that extend CWebDriverTestCase in your tests/functional directory as usual.

Example:

Yii::import( 'ext.webdriver-bindings.CWebDriverTestCase' );
 
class ExampleTest extends CWebDriverTestCase {
 
    protected function setUp() {
        parent::setUp( 'localhost', 4444, 'firefox' );
    }
 
    public function testGoogle() {
        $this->get( 'http://www.google.com/' );
 
        $qElem = $this->findElementBy( LocatorStrategy::name, 'q' );
        $this->assertNotNull( $qElem, 'There is no "query" element!' );
 
        $qElem->sendKeys( array( 'yii framework' ) );
 
        $qElem->submit();
        sleep( 1 );
 
        $elem = $this->findElementBy( LocatorStrategy::className, 'vsc' );
        $this->assertNotNull( $elem, 'Results not found!' );
 
        $this->assertTrue( $this->isTextPresent( 'Yii Framework' ), 'The is no "Yii Framework" text on result page!' );
    }
}

or other way (based on CDbTestCase instead of WebTestCase):

define( 'TEST_BASE_URL', 'http://www.google.com/' );
Yii::import( 'ext.webdriver-bindings.CWebDriverDbTestCase' );
 
class ExampleDbTest extends CWebDriverDbTestCase {
 
    public $baseUrl = TEST_BASE_URL;
 
    public function testGoogle() {
        $this->get( 'http://www.google.com/' );
 
        $qElem = $this->findElementBy( LocatorStrategy::name, 'q' );
        $this->assertNotNull( $qElem, 'There is no "query" element!' );
 
        $qElem->sendKeys( array( 'yii framework' ) );
 
        $qElem->submit();
        sleep( 1 );
 
        $elem = $this->findElementBy( LocatorStrategy::className, 'vsc' );
        $this->assertNotNull( $elem, 'Results not found!' );
 
        $this->assertTrue( $this->isTextPresent( 'Yii Framework' ), 'The is no "Yii Framework" text on result page!' );
    }
}

Resources

Total 10 comments

#10024 report it
pvk at 2012/09/30 10:00pm
ERROR - Exception running 'getLocation 'command on session null

Does anyone had this problem?

I just started the server by: java -jar selenium-server-standalone-2.25.0.jar

And runned the test by: phpunit functional/SiteTestWebDriver.php

10:57:41.201 ERROR - Exception running 'getLocation 'command on session null
java.lang.NullPointerException: sessionId should not be null; has this session been started yet?
    at org.openqa.selenium.server.FrameGroupCommandQueueSet.getQueueSet(FrameGroupCommandQueueSet.java:220)
    at org.openqa.selenium.server.commands.SeleniumCoreCommand.execute(SeleniumCoreCommand.java:55)
    at org.openqa.selenium.server.SeleniumDriverResourceHandler.doCommand(SeleniumDriverResourceHandler.java:613)
    at org.openqa.selenium.server.SeleniumDriverResourceHandler.handleCommandRequest(SeleniumDriverResourceHandler.java:407)
    at org.openqa.selenium.server.SeleniumDriverResourceHandler.handle(SeleniumDriverResourceHandler.java:151)
    at org.openqa.jetty.http.HttpContext.handle(HttpContext.java:1526)
    at org.openqa.jetty.http.HttpContext.handle(HttpContext.java:1479)
    at org.openqa.jetty.http.HttpServer.service(HttpServer.java:914)
    at org.openqa.jetty.http.HttpConnection.service(HttpConnection.java:820)
    at org.openqa.jetty.http.HttpConnection.handleNext(HttpConnection.java:986)
    at org.openqa.jetty.http.HttpConnection.handle(HttpConnection.java:837)
    at org.openqa.jetty.http.SocketListener.handleConnection(SocketListener.java:243)
    at org.openqa.jetty.util.ThreadedServer.handle(ThreadedServer.java:357)
    at org.openqa.jetty.util.ThreadPool$PoolThread.run(ThreadPool.java:534)
10:57:41.202 INFO - Got result: ERROR Server Exception: sessionId should not be null; has this session been started yet? on session null

Am I missing something?

Running on ubuntu 12.04 / firefox 15.0.1.

Thank you very much.

#5209 report it
redguy at 2011/09/22 05:43am
version 1.1b

Just posted version with proper __call handling.

#5207 report it
stereochrome at 2011/09/22 05:15am
Problem with Fixtures

Hi,

i have encountered a problem with database fixtures and CWebDriverDbTestCase this morning.

CWebDriverDbTestCase is overwriting the magic __call method to call methods on its webdriver object.

But CDbTestCase is using __call to redirect calls to fixtures as well. CWebDriverDbTestCase should redirect unknown methods to parent::__call.

Change method __call in CWebDriverDbTestCase (~line 95) to

public function __call( $name, $arguments ) {
  if( method_exists( $this->webdriver, $name ) ) {
    return call_user_func_array( array( $this->webdriver, $name ), $arguments   );
  } else {
    return parent::__call($name, $arguments);
  }
}
#5027 report it
redguy at 2011/09/07 10:39am
New version

Just uploaded new version of our extension.

Changelog:

  • updated core webdriver library from Google Code
  • added some function to CWebDriverTestCase to better fit WebTestCase interface
  • added CWebDriverDbTestCase class for tests based on CDbTestCase, after sergey892 suggestions. Now you can choose wheter to use standard WebTestCase descendant or CDbTestCase descendant in way described by Sergey892
#4573 report it
Sergey Tsivin at 2011/07/21 08:49am
Sharing the browser session between all tests in a test case

I have been experimenting with this extension and I found that it opens a new browser session for each test method of a test case. This is because new session is created in setUp() and destroyed in tearDown().

I find this not convenient because of two reasons:

First, it takes time to close and open a new browser window. The time overhead is not too big though, compared to the time it takes to load the db fixtures.

Second, it seems that cookies are not preserved across the browser sessions. This might be a good thing because you always start from the same environment. But it also means that you have to simulate user login in the beginning of every test method, or, alternatively, put all assertions in a big single test method rather than breaking it into separate tests.

If you want to share the browser session between all tests, here's solution that worked for me:

1. I changed the definition of CWebDriverTestCase:

class CWebDriverTestCase extends CDbTestCase {
 
    public $baseUrl;
    /**
     * @var WebDriver this is the local copy of shared webdriver
     */
    protected $webdriver;
    /**
     * @var WebDriver this is the shared webdriver instance
     */
    private static $_webdriver;
    /**
     * @var string hostname where selenium server is run
     */
    protected static $host = 'localhost';
    /**
     * @var integer port on which selenium server listens
     */
    protected static $port = 4444;
    /**
     * @var string browser that we want to test against
     */
    protected static $browser = 'firefox';
 
    const LOAD_DELAY = 500000; //0.5 sec. delay
    const STEP_WAITING_TIME = 0.5; //when synchronous request is simulated this is single step waiting time
    const MAX_WAITING_TIME = 4; //when synchronous request is simulated this is total timeout when witing for result
 
    public static function setUpBeforeClass()
    {
        self::$_webdriver = new WebDriver(self::$host, self::$port);
        self::$_webdriver->connect(self::$browser);
    }
 
    public static function tearDownAfterClass()
    {
        if (self::$_webdriver) {
            self::$_webdriver->close();
        }
    }
 
    public function setUp()
    {
        $this->webdriver = self::$_webdriver;
        parent::setUp();
    }
...

IMPORTANT: Please note that it is extending from CDbTestCase rather than from CWebTestCase. I could do this because I was writing tests from scratch and was not going to use any of the old Selenium 1 API calls, so I didn't need to extend from PHPUnit_Extension_SeleniumTestCase. Please note that extending from CWebTestCase won't work, because PHPUnit will not call setUpBeforeClass() and tearDownAfterClass() in this case.

2. Then I modified WebTestCase class in my project like this:

<?php
define('TEST_BASE_URL','http://fr.test/');
 
Yii::import( 'ext.webdriver-bindings.CWebDriverTestCase' );
 
class WebTestCase extends CWebDriverTestCase
{
}

I modified it to extend from CWebDriverTestCase. In your project you can override the host, port and browser in this class if needed. I didn't need to override, so this class is essentially empty.

3. Now I can create test cases like this:

<?php
class SiteTest extends WebTestCase
{
 
    public $fixtures = array(
        'user' => 'User',
    );
 
    public function testLogin()
    {
        // Assume we are not logged in
        $this->get(TEST_BASE_URL);
 
        $this->assertTrue($this->isTextPresent('Please login'));
 
        /* @var $element WebElement */
        $element = $this->findElementBy(LocatorStrategy::id, "LoginForm_email");
        $element->clear();
        $element->sendKeys("kuzya@fr.test");
 
        $element = $this->findElementBy(LocatorStrategy::id, "LoginForm_password");
        $element->clear();
        $element->sendKeys("12345");
 
        $element = $this->findElementBy(LocatorStrategy::id, "LoginForm_rememberMe");
        $element->click();
 
        $element = $this->findElementBy(LocatorStrategy::name, "yt0");
        $element->click();
 
        $this->assertTrue($this->isTextPresent('Welcome, Kuzya!'));
    }
 
    public function testFeature()
    {
        $this->markTestIncomplete();
    }
 
    public function testLogout()
    {
        /* @var $element WebElement */
        $element = $this->findElementBy(LocatorStrategy::linkText, "Logout");
        $element->click();
        $this->assertTrue($this->isTextPresent('Please login'));
    }
 
}

4. A couple more minor changes:

I removed the <selenium>.. </selenium> section from the phpuni.xml because it won't work properly with the new extension.

Finally, I made a little change in the bootstrap.php, because WebTestCase.php needs to be included after the application is created, otherwise 'ext' path alias won't work:

<?php
$yiit='/opt/yii/framework/yiit.php';
$config=dirname(__FILE__).'/../config/test.php';
 
defined('YII_DEBUG') or define('YII_DEBUG', true);
 
require_once($yiit);
 
Yii::createWebApplication($config);
Yii::getLogger()->autoFlush = 1;
Yii::getLogger()->autoDump = true;
 
require_once(dirname(__FILE__).'/WebTestCase.php');

Hope, it helps!

#4572 report it
Sergey Tsivin at 2011/07/21 08:06am
API and PhpWebDriver bindings

I found the list of implemented API methods here:

extensions/webdriver-bindings/phpwebdriver/status.html

#4567 report it
redguy at 2011/07/21 03:17am
API and PhpWebDriver bindings

We are waiting for both WebDriver API for PHP providers to merge their code and then we will provide support for this final bindings. I will inform you about new version when available.

#4548 report it
Sergey Tsivin at 2011/07/19 10:17am
Implemented WebDriver API methods

Great work!

It would be nice to see which methods are already implemented. I see there's a page place holder here http://code.google.com/p/php-webdriver-bindings/wiki/implemented_methods but it says "todo" as of the moment...

#4503 report it
redguy at 2011/07/14 04:51am
Difference between classic Selenium and Webdriver (Selenium 2)

Selenium 1 used Javascript to control browser. It was fine for simple web apps, but the more ajax/other javascript logic was embedded in application the more problems it created.

Webdriver (now integral part of Selenium2) uses native browser extension (each browser needs its own implementation) which is controlled by test script. Webdriver tests script can connect directly to controll browser (for example using FirefoxDriver) or remotely through Selenium Server (using JsonWireProtocol).

Generally WebDriver approach is much faster and reliable.

I am not sure if there was possibility to make screenshots in standard Selenium, but WebDriver provides function to do this (i.e. when test fail). You can find more information at http://google-opensource.blogspot.com/2009/05/introducing-webdriver.html and http://code.google.com/p/php-webdriver-bindings/wiki/DifferenceBetweenWebdriverAndSelenium1?ts=1310633014&updated=DifferenceBetweenWebdriverAndSelenium1

#4501 report it
Tibor Katelbach at 2011/07/14 01:18am
difference with WebTestCase

Hi could you please tell us a bit more about the differences between WebDriver and the current WebTestCase ? what are the added values ? is the API richer ?

Leave a comment

Please to leave your comment.

Create extension