Acceptance Testing V - Login PageObject

Creating a Codeception PageObject for the login page

In the previous article, we created a test of the MODX Login form. In this one, we'll take a look at putting the login code into a method that can be used with multiple test files. We'll also look at page objects.

MODX logo

This article assumes that you've installed and configured Composer, Codeception, PhpUnit, MODX, Java, WebDriver, and ChromeDriver as described in earlier articles.

Page Objects

In codeception, a PageObject is usually a class devoted to a single web page. The DOM elements on that page are its properties, and some basic interactions with the page are its methods.

In our example, we'll create a page object for the MODX Login page. The class variables (properties) will be locators and values used to interact with the Login form. We'll also create a method called Login() that will use those variables to log in to the MODX Manager.

Creating the Page Object

Give this command while in the _build directory:

codecept generate:pageobject acceptance LoginPage

The command above will create a LoginPage.php file in the _build/tests/_support/Page/Acceptance/ directory.

Take a look at the code and comments in the LoginPage.php file, then replace it all with this code (also available at GitHub here):

namespace Page\Acceptance;

class LoginPage
    // include url of current page
    public static $managerUrl = 'manager/';
    public static $usernameField = '#modx-login-username';
    public static $passwordField = '#modx-login-password';
    public static $username = 'JoeTester';
    public static $password = 'TesterPassword';
    public static $loginButton = '#modx-login-btn';
    public static $userMenu = '#modx-user-menu';
    public static $logoutLink = "//a[contains(@href,'?a=security/logout')]";
    public static $yesButton = "//button[contains(text(), 'Yes')]";

     * @var $tester \AcceptanceTester;
    protected $tester;

    public function __construct(\AcceptanceTester $I)
        /* Set $this->tester to passed-in $I so it's
            available in other methods */
        $this->tester = $I;

    /** @throws \Exception */
    public function login($username = '', $password = '') {
        /** @var \AcceptanceTester $I */
        $I = $this->tester;
        $username = empty($username) ? self::$username : $username;
        $password = empty($password) ? self::$password : $password;

        $I->waitForElementVisible(self::$loginButton, 20);
        $I->fillField(self::$usernameField, $username);
        $I->fillField(self::$passwordField, $password);
        return $this;

    /** @throws \Exception */
    public function logout() {
        /** @var \AcceptanceTester $I */
        $I = $this->tester;
        $I->waitForElementVisible(self::$logoutLink, 3);

In the code above, we've set the namespace to Page\Acceptance. Codeception is smart enough to look for code in the tests/_support directory. We'll use that namespace in the use line of our test file.

Next, inside the LoginPage class, we set the class variables (properties) first, with the URL of the manager page, the values used fill the Login form, and locators for the fields in the form. For most of the field locators, we've used the CSS id because the Login form code has Ids that are unlikely to change.

Using id locators won't work for two of the fields we need to click on to log out, because (like many Ids in the MODX Manager) their Ids are created on the fly by ExtJS/ModExt and could change at any time. The $userMenu one has an Id. It's the one that triggers the drop-down menu under the Manager user name at the top right. The last two values in the class variables list above ($logoutLink and $yesButton are XPath Locators (more on those in the next article).

The login() method uses the same code we saw in the previous article, but we've added a logout() method, because there may be tests that require it. Notice that we've used a couple of new Codeception methods, moveMouseOver() and waitForElementVisible().

The first one moves the mouse over the element to trigger the drop-down menu (in this particular case $I->click() would work as well).

The second one method waits for the menu item we want to click to appear. The second parameter specifies how long to wait, in seconds.

There is also a waitForElementClickable() method we could have used. Using these will make the code run faster than using the wait() method with some arbitrary value.

Sometimes, page elements don't appear as fast as the test code runs, so clicking on them may result in a element not visible error unless you wait for them to appear. This is especially true of elements that are created with JavaScript.

As we did in previous tests, we've added a few wait() calls so that you have time to see what's happening. They can be removed once the code works.

In addition to PageObjects, Codeception also has StepObjects (created with codecept generate:stepobject acceptance TestName). That creates a StepObject class file in the _support/Step.Acceptance directory.

A step object is very similar to a PageObject (in fact, other than the class file location, I'm not aware of any real differences). Step objects encapsulate methods and (optionally) their associated tests. Some people would have put our login() and logout() methods in a StepObject rather than a PageObject, but I chose to the put methods and their related variables used on a given page together in the same class file because it doesn't involve that much code.

Notice that although we created default values for the username and password fields, we can send them in calls to the login page's login() method, so the class can be used by other tests that need to log in users.

One common approach to StepObjects is to use them for methods that are not tied to a specific web page — for example, a method that creates temporary users or resources directly in PHP rather than using the Manager's built-in processes.

Like PageObjects, StepObjects can contain properties (class variables), methods, and assertions. How to use them (and Helpers) is up to you.

Now, let's create our test file. Give this command in the _build directory:

codecept generate:cest acceptance T14_LoginPage

That will create the T14_LoginCest.php file. Take a look at that file, then replace it with this code (also available at GitHub, here):

The T14_LoginCest.php File

Edit the T14_LoginCest.php file (in the _build/tests/acceptance directory), and replace everything with this code (also available at GitHub, here).

use Page\Acceptance\LoginPage;

class T14_LoginPageCest

    public function _before(\AcceptanceTester $I)


    // tests
    /** @throws \Exception */
    public function testLoginLogout(AcceptanceTester $I)
        $I->wantTo('Log In');
        $loginPage = new LoginPage($I);

At the top of the code, we've put the line, use Page\Acceptance\LoginPage;. That allows us to use new LoginPage when creating the new Login page object, rather than typing in the full namespace path:

$loginPage = new LoginPage($I);

That instantiates a new Login page object so that we can call its login() method.

We also used a new method, $I->wantTo() in the test code. This is a Codeception comment method. It's not an assertion. When you run Codeception in "verbose" mode, it will print the following line in the console:

* I want to Log In

This is useful to show where you are in the code as the test executes. There are also two other comment methods: $I->expect('some text') and $I->expectTo('some text').

After logging in and making sure we are on the Manager page, we log out. The wait() call at the end is there so that you have time to see the logout page.

At the end of the test, we make sure we're back on the login screen with the assertion: $I->see('Password');.

Coming Up

In the next article, we'll look at XPath locators. In the one after that, we'll use our Login page object to perform tests involving more complex operations in the Manager.

For more information on how to use MODX to create a web site, see my web site Bob's Guides, or better yet, buy my book: MODX: The Official Guide.

Looking for high-quality, MODX-friendly hosting? As of May 2016, Bob's Guides is hosted at A2 hosting. (More information in the box below.)

Comments (0)

Please login to comment.