How To Use StepObjects in Cest Classes

CodeceptionCodeception can be used to write automated tests for PHP apps like you’d write with PHPUnit. In fact, Codeception uses PHPUnit under the hood to run unit tests. Codeception also has support for functional and acceptance tests. Here is a tip that took me over an hour of reading the Codeception source code to figure out. Hopefully this will save you some time if you want to use StepObjects in your Cest files.

The Problem

In order to use a StepObject in your Cest file you need to instantiate your StepObject class instead of the base AcceptanceTester class so that you can get access to the custom steps (methods) you defined in the class.

A common scenario for using StepObjects is extracting out common steps that you use in a variety of tests such as signing in to an admin area. So you might write a StepObject class like this:

<?php
namespace AcceptanceTester;

class AdminSteps extends \AcceptanceTester
{
    public function log_in( $username = 'admin', $password = 'password' )
    {
        $I = $this;
        $I->amOnPage( \WpLoginPage::$URL );
        $I->fillField( \WpLoginPage::$username, $username );
        $I->fillField( \WpLoginPage::$password, $password );
        $I->click( \WpLoginPage::$log_in );
    }
}

The documentation for Codeception describes using StepObjects in the context of Cept files which are difficult to organize. In the illustration you see that you need to instantiate your StepObject like this.

$I = new AcceptanceTester\AdminSteps( $scenario );
$I->log_in();

There are a few problems with trying to follow this pattern in your Cest file:

  • How do you get the $scenario?
  • It’s annoying to have to recreate your $I object for all your tests

The Solution

The example code does not show this, but it is mentioned in the Advanced Usage documentation for Cest classes that the $scenario is passed to all public methods as an optional second parameter. So, that’s how you get a hold of the $scenario which you need in order to instantiate your AdminSteps StepObject.

<?php
use \AcceptanceTester;

class WordPressSignInCest
{
    public function signInToAdmin( AcceptanceTester $I, $scenario )
    {
        $I = new \AcceptanceTester\AdminSteps( $scenario );
        $I->wantTo('Sign in to WordPress admin');
        $I->log_in(); // log_in() is defined in AdminSteps
        $I->see('Dashboard', 'h2');
    }
}

Another Solution

You may find it annoying to have to recreate your $I object in the first line of all your tests where you want to use your StepObject. It turns out that you can specify the type of Actor class you want to use in your test’s docblock. Codeception will parse your annotations and look for the Actor class you want to use by looking for @actor AcceptanceTester\AdminSteps in your docblock.

<?php
use \AcceptanceTester;

class WordPressSignInCest
{
    /**
     *  @actor AcceptanceTester\AdminSteps
     */
    public function signInToAdmin( AcceptanceTester $I )
    {
        $I->wantTo('Sign in to WordPress admin');
        $I->log_in();
        $I->see('Dashboard', 'h2');
    }
}

Some people say that using docblocks for annotations that impact the actual code is bad practice because comments should be comments, not code. If you agree, then go with the first solution and catch the $scenario as the second parameter and recreate your $I object. I don’t have a problem using annotations like this – especially in this context. It seems a bit cleaner to me, so I use this second solution with the docblock specifying the type of Actor class to use for the tests.

4 thoughts on “How To Use StepObjects in Cest Classes

  1. Grant Lucas says:

    Great post! I have a fairly large acceptance test suite based on Cept files. I originally wanted to use Cest files but couldn’t get past the exact issue you solve here.

    Might be time to go back for a test refactoring 🙂

  2. Auto Tester says:

    I was having same issue and your first solution works for me. I really wanted to use your second solution as its much cleaner, but for some reason it is not working for me.

    My code is like this..

    /**
    * @actor \Step_Directory\StepObjects_Common_ABC\OnePageUser
    * @actor \Step_Directory\StepObjects_Common_ABC\TwoPageUser
    * @group mySampleTestScript
    */

    public function myTestingScript(AcceptanceGuy $I)
    {
    $I->performActionOne();
    }

    I am getting error “cannot find declaration to goto” when I try to call function from class OnePageUser or TwoPageUser

    Any hint for possible problems and how to resolve it?

    Just to help anyone else who is having same issue, there is another solution for this problem stated on given link https://github.com/Codeception/Codeception/issues/2543

    • Lee says:

      I haven’t used StepObjects in a really long time. I don’t have any hints for solving the problem. Thank you for sharing the link to the alternate solution.

  3. Rebas says:

    here is another option, how use step and page object in cest test

    wantTo(‘some bla bla’);
    $P->automaticLoginAsSuperAdminErp();
    $I->click(NAME_SUPER_ADMIN);
    $S->quickControlPage(
    UserEditPage::$URL,
    UserEditPage::PAGE_TITLE,
    UserEditPage::CONTROL_TEXT,
    SCREENSHOT_ERP . ‘other-editace-uzivatele’
    );
    $I->click(TopBar::USER_MANAGEMENT);
    $S->quickControlPage(
    ErpUserPage::$URL,
    ErpUserPage::PAGE_TITLE,
    ErpUserPage::CONTROL_TEXT,
    SCREENSHOT_ERP . ‘other-erp-uzivatele’
    );
    }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *