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 14 comments

#17420 report it
redguy at 2014/06/09 10:10am
Re: IE click problem vs. native events

passing caps is probably something that should be on our TODO list... as a workaround you can still override startUp method but you need to to load fixtures by yourself (as in oryginal method)

#17316 report it
Bagger1 at 2014/05/22 10:47am
IE click problem vs. native events

Hello again, I've been struggling to get Internet Explorer 11 to click on a link. (Apparently, it has problems if the browser window doesn't have focus.)

I finally made nativeEvents => true in the $allCaps array in WebDriver->connect(), and it worked for me.

I see that there is an optional parameter ($caps) on WebDriver->connect(), but I don't see how to get a custom array sent in to it, since the CWebDriverTestCase->startUp function doesn't include it.

Is there a good way to get the capabilities modified when starting a new test?

#17253 report it
redguy at 2014/05/16 05:00am
update 1.2

Updated webdriver bindings files according to its google code repo. changes are necessary for this extension to work with new Selenium server versions (session handling changed).

pvk and Bagger1 - please check if those changes fix your problems

#17249 report it
Bagger1 at 2014/05/15 02:03pm
CWebDriverTestCase setBrowserUrl() not working

I am using a selenium node and hub and trying to use the example that comes with the CWebDriverTestCase extension.

Even if I put this in setUp ...

$this->setBrowserUrl('localhost');

I get the following error ...

Setbrowserurl() Needs To Be Called Before Start().

/var/lib/mailadmin/vendor/yiisoft/yii/framework/test/cwebtestcase.php: 64

/var/lib/mailadmin/protected/extensions/webdriver-bindings/CWebDriverTestCase.php:156

It appears to me that setBrowserUrl() sets the $baseUrl property, but that the error source seems to want the $browserUrl property to be set.

Does anyone have some help for me?

#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