Quantcast
Channel: Codeception
Viewing all 321 articles
Browse latest View live

Codecepion 1.5.1

0
0

You know, it's a common situation when just after the brand major release comes a small release with patches and bugfixes. Yeah, Codeception 1.5.1 has nothing to surprise you, still it's reommended to upgrade if you are running the phar vs composer issue. As we discovered with the help of alexshelkov, that Codeception packaged phar was trying to load PHP libraries from a local composer installation. So if you was trying phar version in project that already use composer, you may have seen strange errors.

This bug was fixed and last version 1.5.1, ready for download.

A simple improvement was introduced by DevMan. It allows you to execute Codeception from any directory if you set a path to config file with a -c parameter.

php codecept.phar run -c /var/myproject/codeception.yml

And finally we are back green on Travis. We had some issues with Travis infrastructure (not the tests), but we managed to solve them. Did you know that it takes 8 minutes to run all internal tests of Codeception? Yeah, we've got lots of tests )

Build Status

Update

Please redownload your codeception.phar for update:

wget http://codeception.com/codecept.phar -O codecept.phar

Codecepion 1.5.2

0
0

Yet another minor release. It introduces more compact form for unit tests.

<?php
use Codeception\Util\Stub;

class UserRepositoryTest extends Codeception\TestCase\Test
{
    protected function _before()
    {
        $this->user = new User();
    }

    public function testUserIsNotAdmin()
    {
        $this->assertFalse($user->isAdmin());
    }

}
?>

We moved all Codeception's setUp and tearDown routine into parrent class and left you more clean _before and _after methods for your preperations. They act just the same as setUp and tearDown for PHPUnit. So _before is executed before each test and _after after it. Tests should look more clean now. If your tests already have setUp and tearDown methods nothing is changed for them.

Accessing Modules in Unit Tests

Another nice feature for Symfony2 users and not only them. Form now on you can access a module from your unit test. This is done to access public properties of your module. For example, here is how you can retrieve a Symfony Dependency Injection Container:

<?php
protected function _before() 
{
    /**
   * @var $container Symfony\Component\DependencyInjection\Container
   */
    $container = $this->getModule('Symfony2')->container;
    $this->em = $container->get('doctrine');
}
?>

Also we'd like to announce the upcoming modules for Yii and Laravel frameworks.

In the meantime we will release more complete guides on How to integrate framework to Codeception.

Bugfixes

  • Composer build is finally is stable and doesn't require minimal-stability: dev. Thanks to: jacobkiers.
  • Adding support of pecl_http by phpcodemonkey.
  • Methods of Guy received documentation and examples (phar bug).
  • generate:cest command requires a tested class name no more.
  • PostgreSQL driver improved .

Update

redownload your codeception.phar for update:

wget http://codeception.com/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

or via PEAR:

$ pear install codeception/Codeception

Connecting PHP Frameworks. Part 1

0
0

As you know, Codeception has various modules for functional testing of PHP applications. Really, you could use Selenium, but as an advice, please leave Selenium for QAs.

As a developer you need to get more technical details for each test. If test fails you need to see the actual stack trace. As a developer you need to have all test running much faster. You can't wait for an hour before pushing your commit.

If you ever had tried Codeception with Symfony2 you should know how fast are functional tests. You should know how helpful is printed information from profiler: queries used, user authentication status, etc.

In case you want to try functional tests with your own framework you should develop module for it. Basically it's not that hard as you think it is. But some frameworks suit better for integrations and some need additional hooks. In this post I will concentrate on integrating one of the modern PHP frameworks that use HttpKernel Symfony Component.

There are Laravel 4 and Drupal 8 that adopted HttpKernel interface. And more frameworks to come...

So in case you want to integrate Laravel or Drupal, or you know that your framework uses HttpKernel, you should follow this guide.

If you are not aware of what HttpKernel class is, please follow Fabien Potencier's' series of posts Create your own framework... on top of the Symfony2 Components

Contributor's Notice

First of all, how do we treat the user contributions? In Codeception we have liberal politics for accepting pull requests. The only thing, we can't test your implementation, as we just don't have experience in this frameworks. So when you commit module please test your module, and prepare to be it's maintainer. So you will need to write proper documentation and provide support it. Answer to questions on GitHub, in Twitter, etc. Yep, this is opensource.

But we will help you with that. The more developers will use your framework the more contributions your module will receive. So try to encourage framework community to test your module, use it and improve.

When your module is complete it will be packaged with Codeception and it's reference will be published on this site. This is done to make Codeception with all it's modules work out of the box from one phar archive.

Technical Implementation

So you decided to create a module for Codeception that provides integration with HttpKernel. Hope you do!

Check how it is done for Symfony2

  1. We load and initialize HttpKernel class in _initialize method.
  2. Before each test we create a HttpKernel\Client class for this kernel in _before method
  3. We shut down client after each test in _after method.

Let's narrow it to example.

<?php
namespace Codeception\Module;
use Symfony\Component\HttpKernel\Client;

class MyFrameworkModule extends \Codeception\Util\Framework {

    public function _initialize()
    {
        // $app implements HttpKernelInterface
        $app = require_once \Codeception\Configuration::projectDir().'/app.php';
        $app->setEnvironment('test');
        $this->kernel = $app;
    }

    public function _before(\Codeception\TestCase $test)
    {
        $this->client = new Client($this->kernel);
        $this->client->followRedirects(true);
    }

    public function _after(\Codeception\TestCase $test)
    {
        $this->kernel->shutdown();       
    }
}
?>

And basically that's all you need for integration. The Client class has everything to simulate requests and parse responses. Every module that extends Codeception\Util\Framework class will have actions: click, see, fillField, defined and documented in Codeception\Util\FrameworkInterface. This actions will work in just the same manner as for other frameworks. And it's really cool, that testing client is not aware of framework it is testing. All methods and their behavior are just the same. So tests for Symfony2, Zend, or your newly integrated frameworks will look just the same.

Still you may want to add something special for your framework. Maybe some additional initialization steps, or new actions. Let's say a framework you integrate have methods to authenticate a user by name. Why not to use this ability and to make a short cut for logging in?

<?php
namespace Codeception\Module;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Client;

class MyFrameworkModule extends \Codeception\Util\Framework {

    public function _initialize()
    {
        // $app implements HttpKernelInterface
        $app = require_once Codeception\Configuration::projectDir().'/app.php';
        $app->setEnvironment('test');
        $this->kernel = $app;
    }

    public function amLoggedAs($username)
    {
        $this->kernel->getService('session')->authenticate($user);

        $role = $this->kernel->getService('session')->getRole();
        $permission = $$this->kernel->getService('session')->getPermissions();

        // let's display additional information
        $this->debugSection('Role', $role);
        $this->debugSection('Permissions', json_encode($permission));
    }
}
?>

So now we can write a test like this:

<?php
$I = new TestGuy($scenario);
$I->amLoggedAs('davert');
$I->amOnPage('/user/profile');
$I->see('Davert');
?>

As you see, framework integration allows us to access it's internals. Services, classes, configurations, etc. Please add methods that you think may be useful for other developers that will write functional tests. Also you can display some technical details with debug and debugSection methods.

Let other modules get access to framework internals too. In our case we can define kernel as public property and make it accessible from user helper classes.

<?php
class MyFrameworkModule extends \Codeception\Util\Framework {
    public $kernel;
}    
?>

In helper:

<?php 
class TestHelper extends Codeception\Module {

    public function doSomeTrickyStuff()
    {
        $kernel = $this->getModule('MyFrameworkModule')->kernel;
        $kernel->doTrickyStuff();
    }
}
?>

In case you want to provide flexibility you can add configurable parameters to your module. Let's update our example to use additional parameters.

<?php
class MyFrameworkModule extends \Codeception\Util\Framework {
    
    // paramter with default var
    protected $config = array('file_name' => 'app.php');

    // a parameter that we can't guess 
    protected $requiredFields = array('app_name');

    public function _initialize()
    {
        // $app implements HttpKernelInterface
        $app = require_once Codeception\Configuration::projectDir().$this->config['file_name'];
        $app->setEnvironment('test');
        $app->init($this->config['app_name']);
        $this->kernel = $app;
    }
}    
?>

Module won't start if app_name parameter is not set. But file_name parameter is optional and can be redefined.

In the end here how should look functional.suite.yml that includes your newly developed module:

class_name: TestGuy
modules:
  enabled: [MyFrameworkModule, TestHelper]
  config:
      MyFrameworkModule:
          app_name: blog

Keep in mind that main principles of all Codeception modules are simple and smart. This means, if you can guess some parameters - do not require them to be explicitly set.

Conclusion

It's pretty simple to integrate any framework if it's modern and built with Symfony Components. If you have various projects using this framework and you want to get it tested well, try to develop integration. It's not that hard: only 3 methods to implement. Share your work with others, and let it be added to main package.

We appreciate all contributions and all frameworks. Let's unite the PHP world!

Codeception 1.5.3

0
0

Urgent fix for Selenium2 module. It appeared that Selenium sessions were not restarting. The Travis-CI didn't reveal this problem. Selenium tests were passing as usual, just because there were no session related tasks. Main purpose for 1.5.3 was to release this fix. Some other fixes are also included, but it's the most important one.

Modules

In this release we also introduce very basic implementation of Yii module by Ragazzo. If you use Yii, please try it and help us improve Yii integration. To start using, please review the module documentation and issue description on Github.

Also we've got some improvements for AMQP module. Not only queues are cleaned up between tests, but also you can check if messages came to queue, grab them from queues, or publish new messages. This module was tested with RabbitMQ, but it should work with other AMQP providers too. This module is based on php-amqplib.

Generators

From now on you can generate classical PHPUnit tests, with no Codeception stuff in it. Just the plain old PHPUnit tests that you can run natively with PHPUnit itself. For this we have new command generate:phpunit

Use it as regular:

php codecept.phar generate:phpunit NewTest

Also generators now support namespaces. So when you type class name with namespaced, Codeception will generate directories (according to PSR-0) and will put namespace into your newly generated file.

Check out sample usages.

php codecept.phar generate:cest \\Entity\\User
php codecept.phar generate:test \\Entity\\User
php codecept.phar generate:phpunit unit \Entity\\User
php codecept.phar generate:phpunit unit "\Entity\User"

Update

redownload your codeception.phar for update:

wget http://codeception.com/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

Testing Symfony2 Apps with Codeception

0
0

From the beginning of it's existence Codeception was in good relations with Symfony2 framework. Codeception was built on Symfony Components and uses BrowserKit and HttpKernel components for launching functional tests. It's a shame we didn't have a complete Symfony2 integration tutorial before. But we will try to fill this gap today.

What benefits you get by using Codeception with Symfony2? Let's list all of them:

  • user-friendly syntax for functional tests
  • access to container in unit tests
  • testing REST and SOAP services built on Symfony
  • fastest data cleanup when using with Doctrine2

The installation is pretty simple. You can use Composer (as you are used to it), but we'd recommend to try phar package. In this case you can avoid unnecessary dependencies. But all this versions are equal. And when you installed Codeception and executed a bootstrap command you should configure your functional test suite.

In file tests/functional.suite.yml:

class_name: TestGuy
modules:
    enabled: [Symfony2, Doctrine2, TestHelper]

And nothing more. You just need to declare that you will be using Symfony2 and Doctrine2. The Symfony2 module will search for AppKrenel on initialization (in app) and use it for functional tests. Doctrine2 module will find that Symfony2 module is declared and will try to receive default database connection from container. Of course, If you use non-standard setup this behavior can be reconfigured.

Functional Testing

Let's create a first functional test. We use generate:cept functional TestName command.

<?php
$I = new TestGuy($scenario);
$I->wantTo('log in to site');
$I->amOnPage('/');
$I->click('Login');
$I->fillField('username', 'admin');
// ....
?>

And so on. Unlike standard Symfony2 tests you don't need to deal with filters, CSS, and XPaths. Well, you can use CSS or XPath in any selector, if you need to. But on start you can keep your test simple and compact.

The commands we use here are common for most modules that perform testing of web application. That means, that once you discover a need for Selenium, this test can be executed inside a web browser using Selenium2 module. But some commands are unique to Symfony2 module. For example, you can use seeEmailIsSent command that checks if application has submitted an email during the last request. Check Symfony2 module reference for all commands we provide.

Unit Testing

Codeception's unit tests are a bit improved PHPUnit's tests. Inside a unit tests you have access to initialized modules and guy classes.

<?php
class PaginateTest extends \Codeception\TestCase\Test
{
    private $serviceContainer;
    protected $codeGuy;

    protected function _before()
    {
        // accessing container
        $this->serviceContainer = $this->getModule('Symfony2')->container;
        $this->serviceContainer->enterScope('request');
    }

    public function testDefaults()
    {
        // using container
        $this->serviceContainer->set('request', new Request(), 'request');
        $paginate = new Paginate($this->getModule('Symfony2')->container);
        $this->assertEquals(1, $paginate->getCurrentPage());
        $this->assertEquals(0, $paginate->getCount());
        $this->assertEquals(20, $paginate->getLimit());

        // checking data in repository
        $this->codeGuy->seeInRepository('SettingsBundle:Settings', ['pager_limit' => 20]);
    }
}    
?>    

If you want to bypass Codeception hooks for PHPUnit you can create a plain PHPUnit test with generate:phpunit command and get a traiditional PHPUnit's test. Then you can put this test into bundle.

Conclusion

Ok, you will probably ask: why is it better then Behat. We have a wide answer for that. A short is: Codeception is for testing, Behat is for Behavior Driven Development. If you need a professional testing tool that supports PageObject pattern, complex Locators, refactoring capabilities and CodeCoverage - Codeception is a good choice for that.

We say thanks to @everzet for wonderful Mink (that is used for acceptance tests) and to Sebastian Bergmann for it's PHPUnit. Codeception uses their powers, but makes them a bit simpler in use.

Codeception 1.5.4. Skip Tests, UTF fixes, etc.

0
0

Today we are releasing Codeception 1.5.4. This is minor bugfixing release consisting of GitHub issues. And a small yet awaitied feature introduced: ability to skip and mark Cepts as incomplete.

It's pretty simple, though.

<?php
$scenario->skip();

$I = new WebGuy($scenario);
$I->wanTo('do something, but I would rather not');
$I->amOnPage('/');
//.....
?>

This makes this test to be skipped. Similarly it can be marked as incomplete.

<?php
$scenario->incomplete();

$I = new WebGuy($scenario);
$I->wanTo('do something, but I would rather not');
$I->amOnPage('/');
//.....
?>

The skip and incomplete methods can take one argument, that descrines a reason why this tests were marked this way.

<?php
$scenario->skip('waiting for new markup');
$scenario->incomplete('welcome page appearence not tested');
?>

Bugfixes

Update

redownload your codeception.phar for update:

wget http://codeception.com/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

Codeception 1.5.5 and Roadmap Announced

0
0

Yes, here is a new version of Codception with more features and bugfixes in it. We have a flexible relase cycle, so the new version comes when we have a set of updates that you might be needed. We want to say "thank you" for all contributors, and for everyone who helps making Codeption better. It's always nice to get pull requests or follow discussions in our issues secition. So let us sum up the work was done during last 2 weeks and share it with you.

Use Codeception in Different Places

All codeception commands got -c option to provide a custom path to tests. The exception is bootstrap command. It accepts a custom directory as a second argument:

php codecept.phar bootstrap ~/mynewproject
php codecept.phar generate:cept acceptance Login -c ~/mynewproject
php codecept.phar generate:test unit MyFirstTest -c ~/mynewproject
php codecept.phar run -c ~/mynewproject
php codecept.phar generate:scenarios acceptance -c ~/mynewproject

Alternatively you may specify path to codeception.yml file in -c option: php codecept.phar run -c ~/mynewproject/codeception.yml

Thus, you don't need to keep your codecept.phar in the root of your project anymore. Use -c option and one local runner for all your projects.

Skipping Tests

Skipping and marking tests of incomplete was improved. We did a new solid implementation for it (it was very basic in 1.5.4). Now If a test is marked to skip, no modules will be touched.

<?php
$scenario->skip('this is not ready yet, move along');

$I = new WebGuy($scenario);
$I->wanTo('do something, but I would rather not');
$I->amOnPage('/');
//.....
?>

This feature required to rework some core classes (like Step and TestCase and Scenario) but hopefully we simplified our code.

Bugfixes

  • acceptPopup with Selenium 2 does not trigger Exception any more
  • error handling was improved to skip blocked with @ alerts, yet to throw ErrorException for notices, warnings, errors.
  • ZombieJS configuration was fixed. Now the url parameter is required to specify app's local url.
  • REST seeStatusCodeIs method works correctly with Symfony2 now.

Update

redownload your codeception.phar for update:

wget http://codeception.com/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

Roadmap

For the first time we will announce the roadmap for Codeception. Actually we need your opinion: what features you'd like to see in new releases, and what things can be postponed. The list below is not a precise plan, but just a list of features we have in mind:

  • Make a PageObject pattern first-class citizen in Codeception. Add generators and guides to use PageObjects (for acceptance tests).
  • Multiple sessions for tests execution (see discission on GitHub)
  • Silex, Laravel 4, Zend Framework 2, Drupal 8, Phalcon integrations. The key problem here: we can't do this on our own. We need a real users of these frameworks, to create integration and test it on their projects. We have reworked functional testing guide to help you with this. Also use GitHub or personal contacts if you want to make a module.
  • Scenario Unit Tests to be rethinked. We have 2 options here: dump scenario driven unit tests (or mark them as deprecated) or rework them. Actually we need your real opinion. Here is an example of what new Cests may look like. They will dramatically improve the way you work with mocks and stubs in PHP. But will you use it? Please, let us know what you think.

To summarize: we'd appreciate contributions, feedbacks and ideas for next releases.

Codeception 1.5.6

0
0

Codeption 1.5.6 released with varous fixes to Selenium2 module (popups, screenshots) an other bugfixes. We added a bunch of tests for Selenium2 and they are runnng on TravisCI. Also, Codeception is now compatble with Symony 2.2. And few features were intrduced.

Basic Fixtures

Very basic implementation of fixtures was added to Db and Mongo modules. Now you can write something like:

<?php
// for Db
$I->haveInDatabase('users', array('name' => 'davert'));
// for Mongo
$I->haveInCollection('users', array('name' => 'davert'));
?>

to add data for a test. For Db module, the inserted record will be cleaned up on text test, even If cleanup option is set to false. So haveInDatabase method cleans data after itself. Something similar exists for Doctrine2 module. Yet, it looks not like for Db and Mongo implementation:

<?php
$I->persistEntity(new \Entity\User, array('name' => 'Miles'));

Error Handling

From now on you can set custom error handling parameters per suite or globally.

class_name: WebGuy
modules:
    enabled: [PhpBrowser, WebHelper]
error_level: "E_ALL & ~E_STRICT & ~E_DEPRECATED"

Actually, E_ALL & ~E_STRICT & ~E_DEPRECATED is default error level for now. This may be changed in future.

Bugfixes

  • popups and modals in Selenium2 are fully tested and work correctly now
  • screenshots in Selenium2 module is saved correctly
  • seeInField method works correctly even if field is not in form
  • fix to AMQP module to push message to queue
  • Symfony 2.2 compatibility

Update

redownload your codeception.phar for update:

wget http://codeception.com/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

Headless Browser Testing with Selenium2 and PhantomJS

0
0

This is a guest blogpost by Jon Phipps. Jon explains how to use PhantomJS -- the most anticipated testing backend in Codeception.

If you're running acceptance tests in Codeception that require interaction with JavaScript, or have scripts that manipulate the DOM, the speedy Php Browser driver won't help you since it doesn't support JavaScript. The best options for running tests using JavaScript are the Selenium2 and ZombieJS drivers.

The Selenium2 driver actually loads and runs an active browser session, manipulating the browser just as a human would. ZombieJS is a 'headless' browser that provides all of the features of a regular browser, but without a display interface. Without the extra time spent waiting for the display to actually render, a headless browser like ZombieJS can run far faster than a normal browser, so you're tests will execute in as little as half the time. But ZombieJS requires installing Node.js and can be a little buggy, plus it has its own API (which has both pros and cons). The Selenium2 driver is well tested and implements a standard API -- the WebDriver Wire Protocol -- across all of the browsers it has drivers for.

Now there's a headless browser that includes a WebDriver Wire Protocol implementation -- PhantomJS. The latest version of PhantomJS is an easy to install, stand-alone binary that doesn't require installing Node.js or any other dependencies, and ships with its own 'Ghost Driver' for implementing the WebDriver Wire Protocol. Which means you can drive it using the Selenium2 driver in Codeception, and anything that you can test in Chrome, Firefox, Safari, or IE using Selenium2, you can now test in half the time using PhantomJS

Even though it's not needed to run the most recent PhantomJS, it's a good idea to have Selenium2 installed so you can test in other browsers. If you haven't installed Selenium2, you just need to follow the instructions on the Codeception web site for installing and running the Selenium2 driver. It's pretty simple, but totally optional.

To get started with PhantomJS, just download PhantomJS. The binary is setup and ready to use for Linux, Mac OSX, and Windows. Put it, or a symlink to it, somewhere in your path so you can launch it from anywhere.

You're done with installation!

Next, open up a new terminal window and launch PhantomJS, telling it to use the its built-in WebDriver extension to use the port Codeception is listening to (4444 is the default), and leave the window open:

$ phantomjs --webdriver=4444

You should see a response like this in your terminal:

PhantomJS is launching GhostDriver...
[INFO  - 2013-05-10T14:11:05.076Z] GhostDriver - Main - running on port 4444

Now just enable the Selenium2 driver in your acceptance.suite.yml file and use the browser setting browser: phantomjs (an example file is on the Selenium2 driver page). If you're changing modules then you should also run php codecept.phar build.

Check it out by doing a fresh Codeception run:

$ php codecept.phar run acceptance

Your tests should now run quietly and silently, and much faster.

You should see some output in your PhantomJS terminal window providing some useful feedback on this session's capabilities provisioning. This happens on every run (the output below are the defaults):

[INFO  - 2013-05-10T14:33:43.796Z] Session [9dbc5700-b97e-11e2-8dc9-976d2e8732bf] - 
CONSTRUCTOR - Desired Capabilities:{
  "browserName" : "PhantomJS",
  "version" : "9",
  "platform" : "ANY",
  "browserVersion" : "9",
  "browser" : "firefox",
  "name" : "Codeception Test",
  "deviceOrientation" : "portrait",
  "deviceType" : "tablet",
  "selenium-version" : "2.31.0"
}
[INFO  - 2013-05-10T14:33:43.796Z] Session [9dbc5700-b97e-11e2-8dc9-976d2e8732bf] - 
CONSTRUCTOR - Negotiated Capabilities: {
  "browserName" : "phantomjs",
  "version" : "1.9.0",
  "driverName" : "ghostdriver",
  "driverVersion" : "1.0.3",
  "platform" : "mac-10.7 (Lion)-32bit",
  "javascriptEnabled" : true,
  "takesScreenshot" : true,
  "handlesAlerts" : false,
  "databaseEnabled" : false,
  "locationContextEnabled" : false,
  "applicationCacheEnabled" : false,
  "browserConnectionEnabled" : false,
  "cssSelectorsEnabled" : true,
  "webStorageEnabled" : false,
  "rotatable" : false,
  "acceptSslCerts" : false,
  "nativeEvents" : true,
  "proxy" : {
    "proxyType" : "direct"
  }
}
[INFO  - 2013-05-10T14:33:43.796Z] SessionManagerReqHand - _postNewSessionCommand - 
New Session Created: 9dbc5700-b97e-11e2-8dc9-976d2e8732bf

You can adjust these capabilities in your acceptance.suite.yml file like so:

modules:
   enabled: [Selenium2]
   config:
      Selenium2:
         url: 'http://localhost/'
         browser: phantomjs
         capabilities:
             webStorageEnabled: true

I have no idea if capabilities from the larger list of Selenium DesiredCapabilities that are not on the list you see reported from the driver are enabled for PhantomJS.

Headless testing can be a bit of a challenge, since it's impossible to 'see' what failed. But in this case, Codeceptions default logging and screenshot capture on failure can be extremely helpful, since you can then actually see the state of the browser at the point of failure.

There's quite a bit more that you can do with PhantomJS. The CasperJS project makes good use of the PhantomJS API and if you already have NodeJS installed it's a quick and easy way to play with some of the PhantomJS capabilities. CapserJS, aside from requiring NodeJS, only drives PhantomJS. So its not a reasonable alternative to Codeception. Maybe at some point there will be a native Mink driver for the PhantomJS API which will more fully exploit it.

Happy testing.

New Fashioned Classics: BDD Specs in PHPUnit

0
0

One of the most important requirements for tests is maintainability. The tests can live in a project for a months or even years. One day it may happen that some old tests start failing and the team got no idea about what the test does.

Does this test checks something important? Maybe specification has changed and the test should be rewritten? The team who worked on tests that early days wrote them only to make them pass. Team is not sure what was the purpose of test.

In such situations it would have been nice if a developer who wrote the test at least has left some comments. But not. Usually no one documents tests. Test passes and developer is completely satisfied with that fact.

Proper test structure and readability is the only way to maintainable tests. Tests must not turn to a legacy code. Is there a way to write better tests?

The rule, dictated by BDD, is quite simple: write tests for specifications. Do not test just methods, test the behavior. As you know, there are plenty of BDD frameworks that replace classical unit testing with specification testing. You may have heard of RSpec in Ruby, Jasmine, Mocha and others in JavaScript.

If you ever did testing in JavaScript you know how popular mentioned BDD frameworks are. Why can't we have something similar in PHP? We got PHPSpec which is nice, but looks much different from mentioned frameworks. What if we want something more usual? Something like Jasmine in PHP?

Even if we had such BDD framework none will ever adopt it as we have PHPUnit for all kind of testing in PHP. We won't switch PHPUnit in favor of some geeky BDD tool. But actually to write BDD-styled tests, inspired by Jasmine we don't need to do dramatic changes.

We can use Specify, a simple trait inserted into your PHPUnit's TestCase that allows you to store several specifications in a test and write them in BDD way.

At first we will write down some specifications in a body of typical PHPUnit's test:

<?php
// this is just a PHPUnit's testcase
class PostTest extends PHPUnit_Framework_TestCase {

    use Codeception\Specify;

    // just a regular test declaration
    public function testPublication()
    {
        $this->specify('post can be published');
        $this->specify('post should contain a title');
        $this->specify('post should contain a body');
        $this->specify('author of post should not be banned');      
    }
}
?>

Pretty sweet, we started with describing things before the test. But can't we do the same with comments? specify method is much better then comments as it introduces code blocks into PHPUnit.

To see it in action, let's write the tests.

<?php
// this is just a PHPUnit's testcase
class PostTest extends PHPUnit_Framework_TestCase {

    use Codeception\Specify;

    // just a regular test declaration
    public function testPublication()
    {
        $this->post = new Post;
        $this->post->setAuthor(new User());

        $this->specify('post can be published', function() {
            $this->post->setTitle('Testing is Fun!');
            $this->post->setBody('Thats for sure');
            $this->assertTrue($this->post->publish());
        });

        $this->specify('post should contain a title', function() {
            $this->assertFalse($this->post->publish());
            $this->assertArrayHasKey('title', $this->post->errors());        
        });

        $this->specify('post should contain a body', function() {
            $this->assertFalse($this->post->publish());
            $this->assertArrayHasKey('body', $this->post->errors());     
        });

        $this->specify('author of post should not be banned', function() {         
            $this->post->getAuthor()->setIsBanned(true);

            $this->post->setTitle('Testing is Fun!');
            $this->post->setBody('Thats for sure');           

            $this->assertFalse($this->post->publish());
            $this->assertArrayHasKey('author', $this->post->errors());
        });      
    }
}
?>

This code blocks will be executed inside the same test. But it's important to notice that each code block is isolated, thus, when an assertion inside a block fails, the test won't stop the execution. That is how the specify is different from comments.

Now we've got a list of specification and code examples for each case. If one day our site will allow micro-blogging, we can easily find post should contain a body specification and remove it. That's pretty flexible, thus maintainable.

Please note, that all the specification are grouped by context. In plain PHPUnit you would create each code block as a separate method. This way it's pretty hard to all the tests related to one specific feature, in our case - publishing.

Ok, we got nice specifications. But can we also replace classical asserts with some more BDD stuff? Sure. We have another tiny package Verify which is also inspired by Jasmine. Assert keyword is replaced either with expect (as Jasmine does) or verify. This asserts change the order of assetion to improve readability.

Let's rewrite our test with Verify so you could feel the difference.

<?php
// this is just a PHPUnit's testcase
class PostTest extends PHPUnit_Framework_TestCase {

    use Codeception\Specify;

    // just a regular test declaration
    public function testPublication()
    {
        $this->post = new Post;
        $this->post->setAuthor(new User());

        $this->specify('post can be published', function() {
            $this->post->setTitle('Testing is Fun!');
            $this->post->setBody('Thats for sure');
            expect_that($this->post->publish());
        });

        $this->specify('post should contain a title', function() {
            expect_not($this->post->publish());
            expect($this->post->errors())->hasKey('title');       
        });

        $this->specify('post should contain a body', function() {
            expect_not($this->post->publish());
            expect($this->post->errors())->hasKey('body');        
        });

        $this->specify('author of post should not be banned', function() {         
            $this->post->getAuthor()->setIsBanned(true);

            $this->post->setTitle('Testing is Fun!');
            $this->post->setBody('Thats for sure');           

            expect_not($this->post->publish());
            expect($this->post->errors())->hasKey('author');
        });      
    }
}
?>

Basically it's a deal of habit. The expect(XX)->toBe(YY) style is more natural for reading as you read from left to right. But If you got used to assertXXX syntax (where the code is read from right), you can skip Verify library.


With just a few tiny libraries we converted a classical flat PHPUnit test into separate code blocks driven by specification. Test looks very similar to what we saw in Jasmine, just as we intended.

And the piece of advice to your team: introduce a rule "no assertion without specification" to make tests readable and easy to maintain.

You can install Specify and Verify via Composer:

php composer.phar require "codeception/specify:*" --dev
php composer.phar require "codeception/verify:*" --dev

To introduce specify codeblocks, just add use Codeception\Specify into any TestCase file. BDD styled assertions with expect keywords are installed automatically.

Hint: Isolated codeblocks are especially useful for testing exceptions.

Codeception 1.6.8

0
0

Yet another minor release before the 1.7 comes.

In 1.7 you will see new Selenium WebDriver module, better output formatter moved to Symfony Components, and other useful features. It will come shortly during next week(s). But for now lets list bugfixes contributed by our community members. Thank you all!

Bugfixes

  • ZF2 module fixes by Marcelo Araújo.
  • Cleanup for custom enum types in PostgreSQL module by korotovsky.
  • Disabled fields are sent by submitForm method no more. Thx to TrustNik.
  • seeResponseIsXml added to REST module by FnTm. Expect more REST/Xml features in 1.7
  • WebDriver module usage fixed.
  • Fixed unit testing results display when used with CodeGuy object
  • seeElement and dontSeeElement work correctly now.
  • Conditional Asserts work as expected now.
  • seeCookie can be used with different domain set. Thx vkn.
  • fixed submitForm for forms that don't have submit buttons in PhpBrowser by vkn.

Translations

It's good for you to know, that Russian and Portuguese translations of Codeception Guides were started. If you know either of this langueges (or even both, he-he) please, help us get translations done! By now several chapters are already translated, but you know, there are still lot of work,

Update

redownload your codeception.phar for update:

wget http://codeception.com/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

Codeception 1.7: WebDriver

0
0

This is new Codeception with awaited WebDriver module in it. WebDriver module is new incarnation of Selenium implementation. As it was mentioned in previous post this WebDriver module is based on facebook/php-webdriver bindings. The most valuable thing in new Selenium bindings that they can be used just the same way Selenium is used in Java. It's very important project for PHP community, and we say "thank you" to all the Facebook behind it. One day PHP will be used in acceptance testing as widely as Java or Ruby is used.

WebDriver

WebDriver module is pretty new, yet you may want to switch to it from Selenium2. It uses just the same interface, so migration should come smoothly. If you have issues, then no hurry, stay with Selenium2 for a while. Let's list a few features that WebDriver module has:

Implicit waits

<?php
$I->waitForText('foo', 30); // secs
$I->waitForElement('#agree_button', 30);
$I->waitForElementChange('#menu', function(\WebDriverElement $el) {
    return $el->isDisplayed();
}, 100);
$I->waitForJS("return $.active == 0;", 60);
?>

Better Keyboard Manipoluation

<?php
// <input id="page" value="old" />
$I->pressKey('#page','a'); // => olda
$I->pressKey('#page',array('ctrl','a'),'new'); //=> new
$I->pressKey('#page',array('shift','111'),'1','x'); //=> old!!!1x
$I->pressKey('descendant-or-self::*[@id='page']','u'); //=> old!!!1xu
$I->pressKey('#name', array('ctrl', 'a'), WebDriverKeys::DELETE); //=>''
?>

submitForm

A submit form method was implemented in WebDriver.

<?php
$I->submitForm('#login', array('login' => 'davert', 'password' => '123456'));
?>

Common Selenium API

Thus If you got any Selenium questions any answers on StackOverflow will help you. PHP implementation is so close to Java that you can use any answer in PHP.

You can invoke WebDriver methods directly with executeInSelenium

<?php
$I->executeInSelenium(function(\WebDriver $webdriver) {
  $webdriver->get('http://google.com');
});
?>

Symfony Output

You will notice a better output formatting in your console. That's because we migrated to Symfony Console output. As you may know, Symfony console has 3 levels of verbosity, that can set via -v option. Codeception now support them. If you want to get all available information about test, run with -vvv option. The --debug option is now equivalent for running -vv. That's right, you can get even more information with vvv. The output will be improved during the development of 1.7 branch. We hope to get completely different output depending on level of verbosity set.

Cest Annotations

Cests are now much smarter then they were before. If you were using StepObject you might wondered how can you pass step object class into it. That was not really obvious, as by default you get Guy class defined in config. But now you can use guy annotation to specify which guy class to use.

<?php
/**
 * @guy WebGuy\AdminSteps
 */
class AdminCest {

    function banUser(WebGuy\AdminSteps $I)
    {
        // ...
    }

}
?>

Alternatively you can use guy annotation for the method itself.

Also you can now use before and after annotations to define whic methods of Cest class should be executed before the current one. Thus you can move similar actions into protected methods and invoke them via annotations.

<?php
class ModeratorCest {

    protected function login(WebGuy $I)
    {
        $I->amOnPage('/login');
        // ...
    }

    /**
    * @before login
    */
    function banUser(WebGuy $I)
    {
        $I->see('Logout');
        // ...
    }

}
?>

Just by using annotations you can control the invokations of methods of the Cest class. Sure, you should define your support methods with protected, so they won't be executed as tests themselves. Another thing worth to mention, that callbacks defined in after annotation will be called even the main test has failed, thus it makes them useful for clean ups.

We still maintain and bugfix 1.6 branch and there will be 1.6 bugfix releases. The old release 1.6.9 (yep, 1.6.9 was released to with minor bugfixes) can be downloaded from http://codeception.com/releases/1.6.9/codecept.phar.

Removed Dependencies

Optional dependencies were removed from the core, so if you use Composer version and one of the following packages:

    "behat/mink-selenium-driver": "1.1.*",
    "facebook/php-sdk": "3.*",
    "behat/mink-zombie-driver": "1.1.*",
    "videlalvaro/php-amqplib": "*"

you should include them manually in your composer.json. If you use phar version - nothing is changed for you.

Update

redownload your codeception.phar for update:

wget http://codeception.com/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

1.7: Bugfix release

0
0

The release of 1.7 added new WebDriver module as well as rewritten Output component. Some if changes where major and was not tested for all crucial cases. If you feel comfortable with 1.6 you can stay on 1.6 branch. But If you want to get more features with some instabilities - connect to 1.7

Bugfixes in 1.7.1

  • error and failures are now displayed correctly with improved stack traces.
  • fix for module before/after hooks in Codeception\TestCase\Test.
  • select option in WebDriver throws readable message
  • wait in WebDriver throws exception when receives 1000 seconds.

Bugfixes in 1.6.10 (also in 1.7)

  • Fix the problem when the method getPort() return 443 80 or null by thbourlove.
  • Notifies about CURL not installed.

Update

redownload your codeception.phar for update:

1.7.1

wget http://codeception.com/codecept.phar -O codecept.phar

1.6.10

wget http://codeception.com/releases/1.6.10/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

Codeception 1.7.2

0
0

So here is November and a new release with a minor changes is here. The development on 1.8 branch is started and you will get some of a long awaited features soon as well as Phalcon. Btw, you can already try Phalcon module for functional testing (yes, its in master branch, and not inclued in phar yet).

Bugfixes:

  • Updated to be able to use seeOptionIsSelected and dontSeeOptionIsSelected by label in framework modules by piccagliani
  • Update to appendField function to handle checkbox value or label by allmyitjason
  • Removed default delay WebDriver allmyitjason
  • better error description whenever config file could not be found by tiger-seo.
  • Added appendField function into WebDriver by allmyitjason
  • switched to use getInternalResponse in frameworks modules. Fixes "PHP Fatal error: Call to undefined method **\Response::getStatus() " in Symfony2, Laravel4.
  • Fixed PhpBrowser module persisting HTTP authentication between tests by elazar
  • Hardcoded 'localhost' removed for Yii1 by kop
  • CodeCoverage works for WebDriver module as well by allmyitjason
  • Improved output formatting for unit tests by davert

Some of this fixes were also added into 1.6.

Update

redownload your codeception.phar for update:

1.7.2

wget http://codeception.com/codecept.phar -O codecept.phar

1.6.11

wget http://codeception.com/releases/1.6.10/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

Working with PHPUnit and Selenium Webdriver

0
0

In this post we will explore some basics of user acceptance testing with Selenium. We will do this with classical unit testing framework PHPUnit, web browser Firefox, and with new php-webdriver library recently developed by Facebook.

Selenium allows us to record user actions that we do inside a browser and then automate them. PHPUnit will be used to do various assertions and check them for fallacy. And php-webdriver is used to connect PHP with Selenium, in order to do browser manipulation in PHP.

Probably you know, that PHPUnit itself can do Selenium manipulations via PHPUnit. There is PHPUnit_Selenium extension you may use. We will use php-webdriver instead because this implementation is modern and its API is more clean and looks much the same way as original Java Selenium API. That's why it is easier to learn, and much powerful, then the PHPUnit's one. For example, it allows you use send native touch events, which is important in era of mobile-ready web applications.

Grab the tools

Let's install all the required tools using Composer. For this we will need to have composer.json file created:

{
    "require-dev": {
        "phpunit/phpunit": "*",
        "facebook/webdriver": "*"
    }
}

We won't develop any application, thus we are ok, with require-dev section only. Let's run

php composer.phar install

and grab the latest versions of both libraries. We also will need Selenium server executable as well. You need Java installed in order to run the Selenium server. You can launch it by running this:

java -jar selenium-server-standalone-2.37.0.jar

And when the tools are prepared, let's write some tests.

PHPUnit Test

So let's try to test something in the web. Let's start with something simple and well known, like Github. So, let's create a file and call it GitHubTest.

<?php
class GitHubTests extends PHPUnit_Framework_TestCase {
}
?>

As any PHPUnit test it should extend PHPUnit_Framework_TestCase (as it was mentioned, we are not using PHPUnit_Extensions_Selenium2TestCase here). For every test we will need to launch a browser, or in other words, we are starting a Selenium session. This is done by creating RemoteWebDriver instance:

<?php
class GitHubTests extends PHPUnit_Framework_TestCase {

    /**
     * @var \RemoteWebDriver
     */
    protected $webDriver;

    public function setUp()
    {
        $capabilities = array(\WebDriverCapabilityType::BROWSER_NAME => 'firefox');
        $this->webDriver = new \RemoteWebDriver('http://localhost:4444/wd/hub', $capabilities);
    }

}
?>

This initialization part is taken from php-webdriver README file. We don't have any test yet, so let's write something a very basic. Something like: "If I open http://github.com, page title should contain GitHub".

<?php
class GitHubTest extends PHPUnit_Framework_TestCase {

    /**
     * @var \RemoteWebDriver
     */
    protected $webDriver;

    public function setUp()
    {
        $capabilities = array(\WebDriverCapabilityType::BROWSER_NAME => 'firefox');
        $this->webDriver = new \RemoteWebDriver('http://localhost:4444/wd/hub', $capabilities);
    }

    protected $url = 'https://github.com';

    public function testGitHubHome()
    {
        $this->webDriver->get($this->url);
        // checking that page title contains word 'GitHub'
        $this->assertContains('GitHub', $this->webDriver->getTitle());
    }    

}
?>

Now we execute our first test with phpunit

php vendor/bin/phpunit GitHubTest.php

and in a few seconds we should see a Firefox window with Github Page in it

Opening web page with PHPUnit and Selenium

please notice the WebDriver text in the status bar, this tells you that this browser window is controlled by WebDriver.

In a console we will see this output:

PHPUnit 3.7.28 by Sebastian Bergmann.


.

Time: 19.44 seconds, Memory: 1.75Mb

OK (1 test, 1 assertion)

We will see that test has finished, but the browser window stays opened. That is because we did not implement a tearDown method, that should be used to close the webdriver session:

<?php
class GitHubTest extends PHPUnit_Framework_TestCase {

    /**
     * @var \RemoteWebDriver
     */
    protected $webDriver;

    public function setUp()
    {
        $capabilities = array(\WebDriverCapabilityType::BROWSER_NAME => 'firefox');
        $this->webDriver = new \RemoteWebDriver('http://localhost:4444/wd/hub', $capabilities);
    }

    public function tearDown()
    {
        $this->webDriver->close();
    }
}
?>

Advanced Test

We didn't touch any of page elements in a previous test. We just opened the page and checked its title. But the power of webdriver reveals when you want to click elements, fill forms, drag and drop elements, etc. That's why we will write a test that demonstrates some of this features.

But how can control the browser? Should we move the mouse in order to click on element? Well, not exactly. WebDriver allows us to locate element on page by its ID, class name, element name, CSS, or XPath. Let's list all possible locator types, taken from WebDriverBy class:

  • WebDriverBy::className() - searches for element by its CSS class.
  • WebDriverBy::cssSelector() - searches for element by its CSS selector (like jQuery).
  • WebDriverBy::id() - searches for element by its id.
  • WebDriverBy::linkText() - searches for a link whose visible text equals to the value provided.
  • WebDriverBy::partialLinkText() - same as above, but link partly contain the value.
  • WebDriverBy::tagName() - search for element by its tag name.
  • WebDriverBy::xpath() - search for element by xpath. The most complex, yet, most powerful way for element location.

To find element we should use webDriver->findElement method, with locator specified with WebDriverBy.

After the matched element is found we can click on it. Like this:

<?php
class GitHubTest extends PHPUnit_Framework_TestCase {

    /**
     * @var \RemoteWebDriver
     */
    protected $webDriver;

    public function setUp()
    {
        $capabilities = array(\WebDriverCapabilityType::BROWSER_NAME => 'firefox');
        $this->webDriver = new \RemoteWebDriver('http://localhost:4444/wd/hub', $capabilities);
    }

    public function tearDown()
    {
        $this->webDriver->close();
    }

    public function testSearch()
    {
        $this->webDriver->get($this->url);
        // find search field by its id
        $search = $this->webDriver->findElement(WebDriverBy::id('js-command-bar-field'));
        $search->click();
    }    
}
?>

Locating Web Element in Firefox

We are clicking the GitHub global search field, located in top menu bar, matched by its id. By the way, how did we get the element's id? That's a good question. Searching for element locators is the most important task in acceptance testing. For every test we need to get the elements that are involved in it. Let's show some simple tricks that will definitely help you in writing complex acceptance tests.

Locating Elements: Tips & Tricks

The first thing we can do is to pause the test execution. While browser window is still open, we can use it to find the locator. To pause the test execution lets write this helper method inside a test class:

<?php
    protected function waitForUserInput()
    {
        if(trim(fgets(fopen("php://stdin","r"))) != chr(13)) return;
    }
?>

If we use it somewhere in our tests, PHPUnit will wait for Enter key pressed in console before going on.

<?php
    public function testSearch()
    {
        $this->webDriver->get($this->url);
        $this->waitForUserInput(); // paused       
    }    
?>

Now when the browser window is opened we are free to search for required element with no hurry. We are using Firfox Developer Tools for that. With the Element Inspector within we can point to element and get its unique CSS locator.

Using WebDriver with PHPUnit to test GitHub

That is how we got search field id: #js-command-bar-field. Doing the sample steps, let's continue writing our test and find php-webdriver repository on GitHub.

<?php
class GitHubTest extends PHPUnit_Framework_TestCase {

    protected $url = 'http://github.com';
    /**
     * @var \RemoteWebDriver
     */
    protected $webDriver;

    public function setUp()
    {
        $capabilities = array(\WebDriverCapabilityType::BROWSER_NAME => 'firefox');
        $this->webDriver = new \RemoteWebDriver('http://localhost:4444/wd/hub', $capabilities);
    }

    public function tearDown()
    {
        $this->webDriver->close();
    }

    public function testSearch()
    {
        $this->webDriver->get($this->url);
        
        // find search field by its id
        $search = $this->webDriver->findElement(WebDriverBy::id('js-command-bar-field'));
        $search->click();
        
        // typing into field
        $this->webDriver->getKeyboard()->sendKeys('php-webdriver');
        
        // pressing "Enter"
        $this->webDriver->getKeyboard()->pressKey(WebDriverKeys::ENTER);
        
        $firstResult = $this->webDriver->findElement(
            // some CSS selectors can be very long:
            WebDriverBy::cssSelector('li.public:nth-child(1) > h3:nth-child(3) > a:nth-child(1) > em:nth-child(2)')
        );
        
        $firstResult->click();
        
        // we expect that facebook/php-webdriver was the first result
        $this->assertContains("php-webdriver",$this->webDriver->getTitle());
        
        // checking current url
        $this->assertEquals(
            'http://github.org/facebook/php-webdriver', 
            $this->webDriver->getCurrentURL()
        );
    }


    protected function waitForUserInput()
    {
        if(trim(fgets(fopen("php://stdin","r"))) != chr(13)) return;
    }

}
?>

If we run this test we will see that it is failing on the last step:

PHPUnit Test is Failing on Assertion

That's because we forgot that GitHub uses https by default, and GitHub is a company and not a non-profit organization (as we used to think of it, he-he). Though let's change the expected url to 'https://github.com/facebook/php-webdriver' and see the test is passing.

Element Not Found

Probably we will also want to check if element is located on a page. If we were using Selenium2TestCase of PHPUnit, we would have several nice assertion that we can use just for that. In case of php-webdriver library we will need to implement them on our own. But that's pretty easy. Php-Webdriver throws various exceptions, which we can handle and transform into PHPUnit's assertions:

<?php
    protected function assertElementNotFound($by)
    {
        try {
            $this->webDriver->findElement($by);
            // element not found
        } catch (\NoSuchElementWebDriverError $e) {
            // just increment the assertions counter
            $this->assertTrue(true);
            // element not found, as we expected
            return;
        }
        $this->fail("Element not found");
        
    }

?>

You can create similar assertion just in the same manner.

We will use newly created assertElementNotFound method to check that there is no user avatar on "facebook/php-webdriver" page.

<?php
class GitHubTest extends PHPUnit_Framework_TestCase {

    protected $url = 'http://github.com';
    /**
     * @var \RemoteWebDriver
     */
    protected $webDriver;

    public function setUp()
    {
        $capabilities = array(\WebDriverCapabilityType::BROWSER_NAME => 'firefox');
        $this->webDriver = new \RemoteWebDriver('http://localhost:4444/wd/hub', $capabilities);
    }

    public function tearDown()
    {
        $this->webDriver->close();
    }

    public function testGitHubHome()
    {
        $this->webDriver->get($this->url);
        // checking that page title contains word 'GitHub'
        $this->assertContains('GitHub', $this->webDriver->getTitle());
    }

    public function testSearch()
    {
        $this->webDriver->get($this->url);

        // find search field by its id
        $search = $this->webDriver->findElement(WebDriverBy::id('js-command-bar-field'));
        $search->click();

        // typing into field
        $this->webDriver->getKeyboard()->sendKeys('php-webdriver');

        // pressing "Enter"
        $this->webDriver->getKeyboard()->pressKey(WebDriverKeys::ENTER);

        $firstResult = $this->webDriver->findElement(
            // some CSS selectors can be very long:
            WebDriverBy::cssSelector('li.public:nth-child(1) > h3:nth-child(3) > a:nth-child(1) > em:nth-child(2)')
        );

        $firstResult->click();

        // we expect that facebook/php-webdriver was the first result
        $this->assertContains("php-webdriver",$this->webDriver->getTitle());

        $this->assertEquals('https://github.com/facebook/php-webdriver', $this->webDriver->getCurrentURL());

        $this->assertElementNotFound(WebDriverBy::className('avatar'));

    }

    protected function waitForUserInput()
    {
        if(trim(fgets(fopen("php://stdin","r"))) != chr(13)) return;
    }

    protected function assertElementNotFound($by)
    {
        try {
            $this->webDriver->findElement($by);
        } catch (\NoSuchElementWebDriverError $e) {
            $this->assertTrue(true);
            return;
        }
        $this->fail("Unexpectedly element was found");
        
    }
}
?>

Refactoring

To clean up some things we will separate test methods and support methods. It is a good idea to move custom assertions into trait: WebDriverAssertions. And the pause switcher waitForUserInput can be moved into WebDriverDevelop trait. We can enable this trait in a test class, when we develop a test, and turn it off once we finished.

The complete demo project, after this basic refactoring, you can find on GitHub.

And what about Codeception?

So you did notice that this is Codeception blog. But we didn't use Codeception framework at all in this tutorial. Sure, we need to mention, that Codeception includes php-webdriver library and WebDriver module out of the box starting from version 1.7. In Codeception you can perform all the web manipulations in a much simpler manner using the WebGuy APIs. If you use Codeception you don't need to implement your own WebDriver assertions nor write boilerplate code.

Conclusions

No matter you are using Codeception or not it is a good idea to understand how to perform browser based acceptance testing using just the php-webdriver by itself. Php-webdriver library provides very clean and flexible APIs you will enjoy working with.

PHP is a language popular for web development, but not for web testing. Test automation engineers prefer Java and Ruby over it. And there is a serious reason for that. There is no such thing like "official Selenium bindings" for PHP, i.e. there is no Selenium client library for PHP created by Selenium team. Developers of php-webdriver get very close to the official Selenium client APIs. That's why you should use php-webdriver - it really feels and works like native and official Selenium libraries. That is especially important if you have an experience writing acceptance tests in Java or Ruby. Moving to PHP is not that hard, when all the APIs are the same.


Codeception 1.8: Phalcon, Environments, DataProviders

0
0

This release brings lots of changes. Finally we got working DataProviders (the issue was opened for about a year), and @depends tag. But the details below. Let's start with the most important new features:

Phalcon Framework Support

Phalcon

Phalcon is the fastest PHP framework in the world. And that's not just a marketing slogan. Phalcon was developed as C extension, so it is already precompiled and loads into memory only once. Phalcon is modern framework with DI container, ORM (inspired by Doctrine), and templating engine (inspired by Twig). It is one of the most innovative projects in PHP world, so you should at least check it out.

If you already work with Phalcon, here is a good news for you. Codeception got Phalcon1 module, that allows you to write functional tests with minimum setup. Besides standard framework interface this module provides actions for session and database access. It also wraps all tests into nested transactions, and rollbacks them in the end. Thus, all your database tests run superfast.

Is there any Phalcon project tested with Codeception? Yes, it is the official Phalcon's forum.

This module was developed by cujo and improved by davert. We hope you like it.

Environments

This is something you might not expect in the form it was produced, but, probably, that was long awaited. This is to run tests multiple times over different environments. The most common issue is running acceptance tests over different browsers: firefox, phantomjs, chrome. Now you can create 3 different configurations and you can get tests will be repeated 3 times: in firefox, in phantomjs, and in chrome. The second usecase is run tests over different databases.

Feature is pretty straightforward in use. You define the name of environment below the env key, and then you redefine any of configuration values you need.

``` yaml
class_name: WebGuy
modules:
    enabled:
        - WebDriver
        - WebHelper
    config:
        WebDriver:
            url: 'http://127.0.0.1:8000/'
            browser: 'firefox'

env:
    phantom:
         modules:
            config:
                WebDriver:
                    browser: 'phantomjs'

Advanced Usage chapter was updated.

DataProviders

You can use PHPUnit dataproviders in Codeception\TestCase\Test files. Yep. Finally.

Probably DataProviders are not really readable, as you need always to refer into data sets, which may be defined in the different part of a testcase. You can consider using examples of Codeception\Specify library, as for alternative for dataproviders.

Is there a way you can use data providers in scenario driven test? Not exactly, but you can emulate them with loops and conditional asserts:

<?php
foreach ($posts as $post) {
    $I->canSee($post->title,'.post h2');
}
?>

Depends

Declare depending tests in Cest and Test files. Works just as the original @depends of PHPUnit. In Cests you can combine this with @before annotation. More information in Advanced Usage.

Debug

Debug output was refactored, and moved out to Codeception\Util\Debug class. This class can be used globally, i.e in tests, helpers, - wherever you want. To print debug information you should call:

<?php
use Codeception\Util\Debug;
Debug::debug("This is working");
?>

This change dramatically improves debug output. You can also pause execution with pause static method of this class. Useful for debugging and tests development, implemented in WebDriver module as pauseExecution action.

Bugfixes and Minor Changes

  • WebDriver module got pauseExecution method which pauses running test in debug mode.
  • Generated PageObject file URL const was changed to static variable.
  • waitForElementChange() callback return value was not being used by wheelsandcogs | also in 1.7
  • bugfix for making screenshots with WebDriver by Michael Wang. | also in 1.7
  • Doctrine2: handle NULL value in seeInRepository param array by imanzuk | also in 1.7
  • REST: fix adding parameters to url to non-GET HTTP methods by sheershoff
  • REST: Added sendOPTIONS() and sendHEAD() requests for CORS testing by elazar
  • Symfony2: fixed usage of profiler
  • Strict declaratin error fixes for framework constraints | also in 1.6, 1.7

Update

Warning. Module Unit was deprecated in 1.6 and was removed in 1.8. Please disable it in unit.suite.yml if you were using it. If you see this error:

Codeception PHP Testing Framework v1.8.0
Powered by PHPUnit 3.7.28 by Sebastian Bergmann.



  [Codeception\Exception\Configuration]
  Unit could not be found and loaded

Just disable Unit module. Thanks

If you prefer stability over features you can stay on 1.7 or 1.6 releases. We've got them updated too.

redownload your codeception.phar for update:

1.8.0

wget http://codeception.com/codecept.phar -O codecept.phar

1.7.3

wget http://codeception.com/releases/1.7.3/codecept.phar -O codecept.phar

1.6.12

wget http://codeception.com/releases/1.6.12/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

What to expect from 2.0

It is almost 2 years of Codeception, and we are planning to release 2.0 version as a next major to celebrate that. It is a major change, thus we can add few BC breaks. We are planning to:

  • move to PHP 5.4. Not really necessary, yet it is getting hard to support 5.3.
  • remove Mink entirely in favor of WebDriver for browser-testing and Goutte for headless testing.
  • remove 2-times test execution (finally!).
  • ??? (proposed by you)

Selenium WebDriver tests with Codeception

0
0

Last time we discovered API of new WebDriver PHP bindings. We wrote a basic test in PHPUnit that uses Selenium and Firefox browser to find php-webdriver library on Github. Today we will reimplement the same test with Codeception.

Installation

Depending on your preferences you can install Codeception either by downloading codecept.phar archive from the site, or alternatively by using composer.

{
    "require-dev": {
        "codeception/codeception": "*",
    }
}

With composer you will need to execute:

php composer.phar install

In previous tutorial we did installation using Composer, so in current examples we will be using it as well.

Sure, we also need Selenium server executable as well. You need Java installed in order to run the Selenium server. You can launch it by running this:

java -jar selenium-server-standalone-2.37.0.jar

When all installation steps are done, we can continue with creating Codeception bootstrap.

Bootstrap

Unlike the phpunit Codeception requires a small bootstrap step. Codeception helps you to organize tests into 3 categories: acceptance, functional, and unit tests. To create all tests and support directories, you will need to run the bootstrap command.

vendor/bin/codecept bootstrap

Selenium tests are acceptance tests. So let's create a skeleton for the basic acceptance test:

vendor/bin/codecept generate:cept acceptance GitHub

This will create new file in tests/acceptance/GitHubCept.php. But we need some additional configuration to be done before proceeding. Instead of creating webdriver session in tests manually, we delegate this to Codeception. Codeception will take care for creating session before each test, and closing it after. That's why Selenium configuration should be written into tests/acceptance.suite.yml.

class_name: WebGuy
modules:
    enabled:
        - WebDriver
        - WebHelper
    config:
        WebDriver:
            url: 'http://github.com'
            browser: 'firefox'

Each time you change configuration in Codeception you should run the build command.

vendor/bin/codecept build

Writing a Test

Let's start with something very basic. We will open the Github page and we will make sure that GitHub word is within the page title.

<?php
$I = new WebGuy($scenario);
$I->wantTo('see GitHub word in title ');
$I->amOnPage('/');
$I->seeInTitle('GitHub');
?>

We are using the wantTo command just to give a test clean description. amOnPage command opens browser on the github home page, and all the commands that start with see are assertions you can use. There are lots of commands in WebGuy class you can use writing the test. All them are taking from corresponging modules, in our case it is WebDriver module. If you use IDE you can check them all with autocompletion.

Codeception Autocomplete

But let's execute a test with run command:

vendor/bin/codecept run

And you will see this output:

Codeception PHP Testing Framework v1.8.0
Powered by PHPUnit 3.7.28 by Sebastian Bergmann.

Functional Tests (0) ------------------------
---------------------------------------------

Acceptance Tests (1) -------------------------------------------
Trying to see github word in title  (GitHubCept.php)       Ok
----------------------------------------------------------------

Unit Tests (0) ------------------------------
---------------------------------------------


Time: 21.62 seconds, Memory: 5.00Mb

OK (1 test, 1 assertion)

You may have noticed that Codeception is itself powered by PHPUnit. Basically you can execute native PHPUnit tests inside Codeception, but the main idea of Codeception is scenario driven tests written from an eye of a tester. Each test should descibe user's actions in simple words: I see, I click, etc. Let's see how using just this simple terms with no OOP complexity we can write a bit more complex test.

Our test should open github in a browser and use the search form to get to "facebook/php-webdriver" library page. Before writing the test we did some research of GitHub page to find selectors we can use to match elements on page. This was described in previous tutorial.

<?php
$I = new WebGuy($scenario);
$I->wantTo('find facebook/php-webdriver on GitHub');
$I->amOnPage('/');
$I->fillField('#js-command-bar-field','php-webdriver');
$I->pressKey('#js-command-bar-field', WebDriverKeys::ENTER);
$I->click('li.public:nth-child(1) > h3:nth-child(3) > a:nth-child(1) > em:nth-child(2)');
$I->seeInTitle("php-webdriver");
$I->seeCurrentUrlEquals('/facebook/php-webdriver');
?>

You can execute this test and see it passes. Unlike the classical PHPUnit test, this code does not require comments. It is very clean and easy to understand and edit by anyone a team with basic PHP/HTML/CSS skills.

Tips and Tricks

In this section we will share some ideas, that you can use in your tests. Sure that would not be the same tips we have in PHPUnit. But in PHPUnit test we wrote a helper function to pause the execution. This helped us to search for required locators. In Codeception (starting from 1.8) we can use $I->pauseExecution(); method for this. The test will stop the scenario in that place and wait for Enter to be pressed. Worth to mention, that pauseExecution works only in debug mode (sure, you don't want to freeze when running tests on CI). So you should pass --debug option when using it.

But even Codeception provides us with dozen of predefined commands, we might want to access webdriver session by your own. For this we have a executeInSelenium command:

<?
$I->executeInSelenium(function(RemoteWebDriver $webDriver) {
    $webDriver->getKeyboard()->pressKey(WebDriverKeys::ENTER);
});
?>

But that doesn't make our test more readable. What can we do to simplify it a bit? Well, let's replace this double search for '#js-command-bar-field' input field. We can use submitForm command that works for forms on any kind, and you can pass array of field values into it. Current form id is top_search_form, so we can rewrite our test like:

<?php
$I = new WebGuy($scenario);
$I->wantTo('find facebook/php-webdriver on GitHub');
$I->amOnPage('/');
$I->submitForm('#top_search_form', array('q' => 'php-webdriver'));
$I->click('li.public:nth-child(1) > h3:nth-child(3) > a:nth-child(1) > em:nth-child(2)');
$I->seeInTitle("php-webdriver");
$I->seeCurrentUrlEquals('/facebook/php-webdriver');
?>

When running WebDriver tests with Codeception they may look a bit slower then the same tests run with PHPUnit. That's because Codeception has default delay of 5 seconds for each test action on page. This delay can be set to 0 in order to run test faster. This is configured in acceptance.suite.yml

class_name: WebGuy
modules:
    enabled:
        - WebDriver
        - WebHelper
    config:
        WebDriver:
            url: 'http://github.com'
            browser: 'firefox'
            wait: 0

This should execute the same test 30% faster then before. But if your page use JavaScript and Ajax, you should increase the wait time or use various waitFor commands of WebDriver.

Conclusion

At first sight setting up Codeception Selenium test may look a bit harder then with PHPUnit. At first sight it's hard to see real benefits in it. But the key idea of Codeception is to separate the test code and support code. So you should write only tests, and most of helper methods was already written for you. Thus, the test is kept clean and readable. It is easy to change and easy to manage. If you want to switch a browser, you should not edit a test, but change the configuration. You want to execute test in 2 browsers one by one? No problems, just change a configuration.

Codeception is very flexible framework that you can use to write your Selenium tests. After all it's really simple and fun.

Source file of this tutorial are available on GitHub.

Codeception 1.8.1: Minor updates

0
0

Codeception 1.8.1 is out. Bugfixes and small useful features in it. The most interesting improvement was done by frqnck. Phar version now has self-update command which acts the same way as it is in composer. From now on you can easily upgrade codeception.phar file.

Small yet important change in WebDriver module. Default wait parameter is set to 0. This was done because Selenium implicit waits didn't work as expected - this parameter slowed down test execution. Browser waited for element even if it was already on a page. Please notice this on upgrading.

Changes

  • upgraded to php-webdriver 0.3
  • added general sendAjaxRequest() method to all framework/phpbrowser modules to send ajax requests of any types by gchaincl.
  • fixed URI construction in Yii1 module by kop(also in 1.7)
  • Fixed Yii2 statusCode by cebe
  • fixed: Placeholder\Registry::unsetRegistry() should only be used with < 2.2.0 by Bittarman
  • waitForElementChange() was fixed in WebDriver module
  • fixed seeLink and dontSeeLink methods in framework modules by enumag.
  • added seeHttpHeaderOnce to REST module for checking if headers appear only once.
  • setUpBeforeClass/tearDownAfterClass method will work as they are expected in PHPUnit
  • Debug::debug can output any variable in console.
  • fixed: "WebDriver makeScreenshot doesn't create directories" by joksnet
  • fixed grabValueFrom method in framework modules (also in 1.7)
  • fixed: "Disable remote coverage not work" by tiger-seo (also in 1.6, 1.7)
  • self-update command added to phar by frqnck

Dependencies were updated, thus Symfony components were updated to 2.4. No changes to composer.json was made, so this release is compatible with Symfony 2.3 as well.

Update

redownload your codeception.phar for update:

1.8.1

wget http://codeception.com/codecept.phar -O codecept.phar

1.7.4

wget http://codeception.com/releases/1.7.4/codecept.phar -O codecept.phar

for composer version

$ php composer.phar update

Testing Emails in PHP. Part 1: PHPUnit

0
0

So how do you check that your applications sends email correctly? It looks like dealing with emails is always a challenge. How would you verify that email message is formatted and delivered correctly, without actually sending them to your clients? That's the first question. And the second question is: how can we automate testing of emails?

For both questions we have an answer. There are two awesome services that is developed to help developers in dealing with email hell. It is Mailtrap and Mailcatcher. Both services run SMTP server that does not deliver emails, but store them locally. They both have a web interface in which you can review all the outgoing emails. The difference between this services are: mailtrap runs as a web service, and mailcatcher is a ruby gem that can be installed locally.

mailcatcher

Its up to you which one to use. Definitely they will simplify your life while developing a web application. Do they have something to offer for testing? Sure! We can access all handled emails via REST API and verify our assertions.

In this post we will marry Mailcatcher with PHPUnit testing frameworks. We've chosen Mailcatcher for not to rely on 3rd-party web service and have all the tests run locally. We will write methods for both PHPUnit and Codeception in order to provide different solutions and compare them.

Before we start we need to make sure that Mailcatcher is installed and running. When done you can access its web interface on 1080 port and use 1025 port for fake SMTP server. Configure your web application to use exactly that port in your application when running in test environment.

Testing emails in PHPUnit

Mailcatcher has a really simple REST API that is used for email access. Here is a quote from their official site.

A fairly RESTful URL schema means you can download a list of messages in JSON from /messages, each message's metadata with /messages/:id.json, and then the pertinent parts with /messages/:id.html and /messages/:id.plain for the default HTML and plain text version, /messages/:id/:cid for individual attachments by CID, or the whole message with /messages/:id.source.

What was not mentioned that you can also clear all emails by sending DELETE request to /messages. The most complete documentation on API is its code. Even you don't know Ruby it is really fair.

Thus, we will need to send GET and DELETE requests and parse json response. To send them we will use Guzzle framework. PHPUnit and Guzzle can be easily installed via Composer:

{
    "require-dev": {
        "phpunit/phpunit": "*",
        "guzzle/guzzle": "~3.7"
    }
}

Let's create EmailTestCase file and place MailCatcher API calls into it.

<?php
class EmailTestCase extends PHPUnit_Framework_TestCase {

    /**
     * @var \Guzzle\Http\Client
     */
    private $mailcatcher;

    public function setUp()
    {
        $this->mailcatcher = new \Guzzle\Http\Client('http://127.0.0.1:1080');

        // clean emails between tests
        $this->cleanMessages();
    }

    // api calls
    public function cleanMessages()
    {
        $this->mailcatcher->delete('/messages')->send();
    }

    public function getLastMessage()
    {
        $messages = $this->getMessages();
        if (empty($messages)) {
            $this->fail("No messages received");
        }
        // messages are in descending order
        return reset($messages);
    }

    public function getMessages()
    {
        $jsonResponse = $this->mailcatcher->get('/messages')->send();
        return json_decode($jsonResponse->getBody());
    }
?>    

That's enough for fetching list of all delivered emails. All the emails will be cleaned between tests, so test will be executed in isolation. Let's implement some assertion methods to check sender, recipient, subject and body of the email.

<?php
    // assertions
    public function assertEmailIsSent($description = '')
    {
        $this->assertNotEmpty($this->getMessages(), $description);
    }
    
    public function assertEmailSubjectContains($needle, $email, $description = '')
    {
        $this->assertContains($needle, $email->subject, $description);
    }

    public function assertEmailSubjectEquals($expected, $email, $description = '')
    {
        $this->assertContains($expected, $email->subject, $description);
    }

    public function assertEmailHtmlContains($needle, $email, $description = '')
    {
        $response = $this->mailcatcher->get("/messages/{$email->id}.html")->send();
        $this->assertContains($needle, (string)$response->getBody(), $description);
    }

    public function assertEmailTextContains($needle, $email, $description = '')
    {
        $response = $this->mailcatcher->get("/messages/{$email->id}.plain")->send();
        $this->assertContains($needle, (string)$response->getBody(), $description);
    }

    public function assertEmailSenderEquals($expected, $email, $description = '')
    {
        $response = $this->mailcatcher->get("/messages/{$email->id}.json")->send();
        $email = json_decode($response->getBody());
        $this->assertEquals($expected, $email->sender, $description);
    }

    public function assertEmailRecipientsContain($needle, $email, $description = '')
    {
        $response = $this->mailcatcher->get("/messages/{$email->id}.json")->send();
        $email = json_decode($response->getBody());
        $this->assertContains($needle, $email->recipients, $description);
    }
?>

The complete code listing is published as gist.

Example

How a test using this may this EmailTestCase class may look like?

<?php      
    function testNotificationIsSent()
    {
        // ... trigger notifications

        $email = $this->getLastMessage();
        $this->assertEmailSenderEquals('<bugira@bugira.com>', $email);
        $this->assertEmailRecipientsContain('<davert@ukr.net>', $email);
        $this->assertEmailSubjectEquals('[Bugira] Ticket #2 has been closed', $email);
        $this->assertEmailSubjectContains('Ticket #2', $email);
        $this->assertEmailHtmlContains('#2 integer pede justo lacinia eget tincidunt', $email);
    }
?>    

We got a really simple class for testing emails from your application. Ok, that's not a unit testing. For unit testing you should use mocking framework to fake the delivery in your PHP code. But you can use this class in acceptance tests (with Selenium) or integration tests. It is much simpler to test emails this way, then to dig into internals of your email sending library and defining mocks. The drawback here is usage of standalone daemon, and reconfiguring your application to use its SMTP server.


It looks like this post is long enough to be published. We will continue email testing next time with Codeception framework. We will develop EmailHelper class for scenario-driven tests of Codeception.

EmailTestCase Source

Codeception 1.8.2: Bugfixes

0
0

Time passed since the previous release. Since 1.8.1 we got a nice list of significant bugfixes and we are ready to publish new stable release. No new features added, actually, but they are coming in new 2.0 branch which is actively developed in this days.

Changes:

  • [REST] match similar elements in JSON arrays fixed in #837 by blacknoir.
  • generate:pageobject now takes -c option correctly #809
  • [REST] Fixed setting Content-Type header #827
  • [REST] Headers are uppercased according to BrowserKit standard.
  • [Db] records inserted with haveInDatabase now are cleaned in _after event by dirk-helbert #761.
  • [Laravel] Fixed usage of Redirect::back in tests
  • Fixed collecting CodeCoverage using WebDriver/Selenium2 modules.
  • [REST] Fixed "Call to undefined method Symfony\Component\HttpFoundation\Request::getCookies() in codeception/codeception/src/Codeception/Module/REST.php line 352" by casconed #814
  • Fixed: tests run twice if you use .dist.yml config by tomtomsen #582
  • Environments: Test classes was loaded only once in multi-environment mode. Fixed by ayastreb and his nice tokenizer solition #812
  • Excluding running abstract classes in addCest by filipgorny #792
  • [PhpBrowser] Fixed setting cookies from headers
  • [Framework] Form data on page was not cleaned after form submit. So when sending the same form twice, cached data was submitted. This is now fixed.
  • and others...

Also we've got a Mockery module for using powerful Mockery framework with Codeception. It is not included in Codeception itself but can be installed via Composer. (Thanks to Jáchym Toušek)

Thanks to all contributors, thanks for getting bug fixed and reported. We are trying to be better with each release.

Update

redownload your codeception.phar for update:

1.8.2

php codecept.phar self-update

for composer version

$ php composer.phar update codeception/codeception

What's Next

We are moving to Codeception 2.0. It's first alpha is expected on next week. Stay tuned and get ready for new features!

While developing Codeception 2.0 we released a lightweight task runner Robo. We needed it to run routine tasks for Codeception - building phar archives, merging releases, etc. You may use it as lightweight alternative for Phing or PHP alternative for shell scripts. It's not very documented, it doesn't include list of all required tasks, but it will evolve. If you have ideas on improvement, or you want to add more tasks into it - please send Pull Requests.

And thanks to @pfaocle for the feedback on using Robo!

Viewing all 321 articles
Browse latest View live




Latest Images