Automation with Codeception + Gherkin + PageObject for the smallest

Not finding on the Internet a single concrete example of the Gherkin implementation with the Page Object design pattern for Codeception , it was thought that it would not be superfluous to tell the Internet about its own implementation of this pattern.

This article is intended more likely for those who are already a little familiar with Codeception or similar frameworks, but still do not know how using Page Object to make tests more readable, to simplify their support and to reduce the amount of excess code. Nevertheless, I tried step by step to explain all the main points of the assembly of the automation project from scratch.

The Page Object template allows you to encapsulate work with page elements, which in turn allows you to reduce the amount of code and simplify its support. All changes to the UI are easily and quickly implemented - just update the Page Object class that describes this page. Another important advantage of this architectural approach is that it allows you not to clutter up the HTML test script with details, which makes it more understandable and easy to read.

This is how the test looks like without the use of a Page Object.

With the use of a Page Object

I will not dwell on the installation of the base environment;

  • Ubuntu bionic beaver
  • PHP 7.1.19-1
  • Composer - dependency manager for PHP, installed globally
  • PhpStorm - development environment

To run the tests, we also need:

Expand Codeception

We proceed to the installation of Codeception:

We open in the terminal the directory we need, where we will build the project, or create a directory for the project and go to it:

mkdir ProjectTutorial
cd ProjectTutorial

Install the Codeception framework and its dependencies:

composer require codeception/codeception --dev

The installation file of the composer.json dependencies in the project will look like this:

   "require": {
       "php": ">=5.6.0 <8.0",
       "facebook/webdriver": ">=1.1.3 <2.0",
       "behat/gherkin": "^4.4.0",
       "codeception/phpunit-wrapper": "^6.0.9|^7.0.6"
   "require-dev": {
       "codeception/codeception": "^2.5",
       "codeception/base": "^2.5"

We develop the project:

php vendor/bin/codecept bootstrap

More information about how to install the project can be obtained in the official documentation .

At this stage, three tests (suite) are created in our project. By default, Codeception separates them into acceptance, functional, and unit. To these sets, Codeception also generates three yml files. In them we specify all the necessary configurations , we connect the modules and properties to run our tests.

This lesson is built on the example of the Acceptance test, so I will bring the settings to Acceptance.suite.yml.

Open our project in PHP Storm (or another favorite development environment) and go to Acceptance.suite.yml (by default it lies in the tests / acceptance.suite.yml folder).
We prescribe the minimum necessary dependencies and be sure to pay attention to formatting. The modules are separated by a "-" and must be located on the same level, otherwise errors will sprinkle when the test is started.

It turns out:

actor: AcceptanceTester
    - WebDriver:
        url: ''//тут может быть любой сайт, для которого вы будете писать свой сценарий
          browser: 'chrome'
    - \Helper\Acceptance //в этом модуле будут находиться методы взаимодействия с элементами страницы
       - AcceptanceTester

And a little more preparatory work:

Let's create a separate directory in the root of the project (I have a lib).
In this directory, create an executable file, which will run Selenium and Chrome Driver.

We put Selenium and Chrome Driver here, and write the run command to

java -jar selenium-server-standalone-3.14.0.jar

How it looks in the project:

Go back to the console and change access rights:

chmod +x ./

(note. The names of the drivers in the directory must exactly match those specified in the start command).

You can start Selenium and Webdriver right now so that you don’t have to return to it. To do this, open a new tab of the terminal, go to the directory where the file is located and write the start command:

~/AutomationProjects/ProjectTutorial/lib$ ./

Make sure that the server is running:

We leave it in a running state. This preparatory work is completed.

Writing a test script

Moving on to creating a feature file for our test script. To do this, a special command is provided to Codeception, run it in the console:

cept g:feature acceptance check

(REM. “check” - the name of my test)

We see in the folder of acceptance a new file check.feature.

We do not need the default content, immediately delete and write our test.

In order for the collector to recognize the Cyrillic alphabet, do not forget to start the script with #language: ru.
We write a short script in Russian. I remind you that each sentence must begin with the keywords: “When”, “Then”, “And”, the symbol “*”, etc.

For my example, I took the Yandex site, you can take any.

To see what steps there are in the test, we run our script in the terminal:

cept dry-run acceptance check.feature

The script steps are output to the console, but they are not yet implemented.

Then we run a command that automatically generates templates for implementing our methods:

cept gherkin:snippets acceptance

All names from the script that were in quotes are replaced with variables: arg.
We copy them from the terminal and paste into the AcceptanceTester.php file , where the methods of working with page elements will lie.

Rename the methods to readable, reflecting their essence (optional), and write their implementation.

Everything is simple, but even easier if you work in a smart development environment, such as Storm, which itself will prompt the necessary commands from the library:

Delete the excess and write the methods:

* @When пользователь находится на Главной странице 
* @Then на странице присутствует :element
* @Then пользователь нажимает на :button
* @Then вводит в поле :field текст :text
*/publicfunctionstep_fillField($field, $text){
   $this->fillField($field, $text);

Let's see what happened. Run the command that will show us which methods (step) we now have implementation.

cept gherkin:steps acceptance


But the steps in the feature file are still not recognized as methods.

To Storm understand what to do with the steppes, let's check the trick with the implementation of the Context interface from the namespace Gherkin Context.

namespace Behat\Behat\Context {

Wrap our AcceptanceTester class in a namespace and inherit from Context.

implements \Behat\Behat\Context\Context

Now all the steps of a feature file are tied to their implementation:

In order for Webdriver to understand what to click and where to look, you need to replace the readable element names and page addresses with appropriate locators and URLs that will be included in the methods in the form of arguments.

Then we get a test of the form:

And you can run:

cept run acceptance


approx. If the page elements take a long time to load, you can add a wait to the desired method:


We return to our test script. We are upset that all the readability of the test is lost due to the fact that instead of the clear names of elements and pages we see HTML elements and url.

If we want to fix this, it's time to move on to implementing the Page Object pattern.

Go to Page Object

In the _support directory we create the Page directory where we will add our class pages:

php vendor/bin/codecept generate:pageobject MainPage

The first Page Object is the main Yandex page, we will call it MainPage, the class will be called the same:

Here we declare static fields and methods so that they can be called without creating an object.

Since in the configurations of Acceptance.suite.yml we have already indicated the starting page url: , then for the main page it will be sufficient to specify

publicstatic $URL = '/';

Next comes the array of page elements. We describe locators for several elements and give them clear and unique names.

Now you need to add the getElement method, which will return the locator by the name of the element from the array.

As a result, we have:

<?php//location: tests/_support/Page/MainPage.phpnamespacePage;
/** Главная страница */classMainPage{
   publicstatic $URL = '/';
   publicstatic $elements = array(
       'раздел Афиша' => "//*[@id='wd-wrapper-_afisha']",
       'гиперссылка Афиша' => "//*[@data-statlog='']",
       'иконка Погода' => "//*[@class='weather__icon weather__icon_ovc']|//*[@class='weather__icon weather__icon_skc_d']", 

I’ll add a couple of classes of pages:

/ ** Playbill * /

/ ** Playbill - Search results * /

Go back to the AcceptanceTester.php class , where we wrote our methods.
Create an array of PageObject classes in it, where we assign names to pages and specify their class names in the namespace:

private $pages = array(
            "Главная страница" => "\Page\MainPage",
            "Афиша" => "\Page\AfishaPage",
            "Афиша - Результаты поиска" => "\Page\AfishaResult"

Each new PageObject is similarly added to this array.

Next, we need to create the currentPage field in which the link to the PageObject of the current page will be stored:


Now we will write a method, by calling which we will be able to get currentPage and initialize the PageObject class we need.

It is logical to make a step like this “When the user navigates to the page title page.” Then the easiest way to initialize the PageObject class, without checks, will look like this:

* @When пользователь перешел на страницу :page
   // Инициализируется нужный pageObject $this->currentPage = $this->pages[$page];

Now we write the getPageElement method, which will allow us to get an element, or rather, its locator from the current page:

   //Берет нужный элемент по его имени с нужной страницы
   $curPage = $this->currentPage;
   return $curPage::getElement($elementName);

For already implemented methods, it is necessary to replace the arguments that we initially received directly from the feature text with elements from PageObject, that is:


take the form


Then our methods will take the form:

* @When пользователь находится на странице :page
   // Открывается страница и инициализируется нужный pageObject$this->currentPage = $this->pages[$page];
   $curPage = $this->currentPage;
* @Then на странице присутствует :element
* @Then пользователь нажимает на :button
* @Then вводит в поле :field текст :text
*/publicfunctionstep_fillField($field, $text){
   $this->fillField($this->getPageElement($field), $text);
* @Then пользователь удаляет текст в поле :field

Added another method to display the search results by pressing the Enter key:

* @Then нажимает на клавиатуре ENTER

The last step is when all the necessary methods and PageObjects are described, you need to refactor the test itself. Add steps that will initialize PageObject when moving to a new page. We have this “* user navigated to page: page”.

For clarity, I will add a few more steps. The result is the following test:

#language: ru
Функционал: Поиск на Яндекс Афише
 Сценарий: Открываем главную страницу. Переходим в раздел Афиша. Ищем хорошее кино.
   Когда пользователь находится на странице "Главная страница"Тогда на странице присутствует "иконка Погода"Тогда на странице присутствует "раздел Афиша"
   И пользователь нажимает на "гиперссылка Афиша"Тогда пользователь перешел на страницу "Афиша"
   И вводит в поле "строка Поиска" текст "Зелёный слоник"
   И нажимает на клавиатуре ENTER
   Тогда пользователь перешел на страницу "Афиша - Результаты поиска"
   И на странице присутствует "События в ближайшие дни"Тогда пользователь удаляет текст в поле "строка Поиска"
   И вводит в поле "строка Поиска" текст "Головаластик"
   И нажимает на клавиатуре ENTER

Such a test script is understandable and readable for any stranger.


To view a more detailed result of the run, you can use the command

cept run acceptance --debug

See the result:

Thus, using the Page Object pattern allows you to separate all page elements from test scripts and store them in a separate directory.

The project itself can be found at

As a beginning automator, I would be grateful if you share your ideas and, perhaps, tell me how logical it is to transform and simplify the structure of the project.

Also popular now: