56

I'm looking for a simple example of code with the pageObject design pattern and gherkin because when I follow the codeception BDD documentation, all examples written in the tests/support/AcceptanceTester.php. I don't understand (poor English skills --) how not concentrate all code in the AcceptanceTester.php file.

For example, I have a sample home page with two buttons A and B. If the user clicks on button A, page A is loaded else if the user clicks on button B, page B is loaded.

Currently, my AcceptanceTester :

<?php
// tests/_support/AcceptanceTester.php
/**
 * Inherited Methods
 * @method void wantToTest($text)
 * @method void wantTo($text)
 * @method void execute($callable)
 * @method void expectTo($prediction)
 * @method void expect($prediction)
 * @method void amGoingTo($argumentation)
 * @method void am($role)
 * @method void lookForwardTo($achieveValue)
 * @method void comment($description)
 * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
 *
 * @SuppressWarnings(PHPMD)
 */

class AcceptanceTester extends \Codeception\Actor
{
    use _generated\AcceptanceTesterActions;

    /**
     * @Given The home page
     */
    public function inHomePage()
    {
        $this->amOnPage("/");
        $this->seeInTitle('home');
    }

    /**
     * @When I click on the button A
     */
    public function goToThePageA()
    {
        $this->click(['name' => 'A']);
    }

    /**
     * @Then l go to the page A
     */
    public function ImInPageA()
    {
        $this->seeInTitle('page A');
    }

    /**
     * @When I click on the button B
     */
    public function goToThePageB()
    {
        $this->click(['name' => 'B']);
    }

    /**
     * @Then l go to the page B
     */
    public function ImInPageB()
    {
        $this->seeInTitle('page B');
    }
}

If I run the command './vendor/bin/codecept run acceptance', all works like a charm. But as I said previously, I need to learn how don't concentrate all code in the AcceptanceTester file.

So, I created three pageObjects ; one for the home page, one for the page A and one for the page B. The code :

the home pageObject :

<?php
// tests/_support/Page/PageHome.php
namespace Page;

class PageHome
{
    public static $URL = '/home';
    public static $title = "home";
    public static $aButton = ['name' => 'A'] ;
    public static $bButton = ['name' => 'B'] ;

    public static function route($param){
        return static::$URL.$param;
    }

    /**
     * @var \AcceptanceTester;
     */
    protected $acceptanceTester;

    public function __construct(\AcceptanceTester $I){
        $this->acceptanceTester = $I;
    }
}

the A pageObject :

<?php
// tests/_support/Page/PageA.php
namespace Page;

class PageA
{
    public static $URL = '/home/pageA';
    public static $title = "page A";

    public static function route($param){
        return static::$URL.$param;
    }

    /**
     * @var \AcceptanceTester;
     */
    protected $acceptanceTester;

    public function __construct(\AcceptanceTester $I){
        $this->acceptanceTester = $I;
    }
}

And the B pageObject :

<?php
// tests/_support/Page/PageB.php
namespace Page;

class PageB
{
    public static $URL = '/home/pageB';
    public static $title = "page B";

    public static function route($param){
        return static::$URL.$param;
    }

    /**
     * @var \AcceptanceTester;
     */
    protected $acceptanceTester;

    public function __construct(\AcceptanceTester $I){
        $this->acceptanceTester = $I;
    }
}

Then, I created three stepObjects ; homeChecker, goToPageA, goToPageB

The homeChecker stepObject :

<?php
// tests/_support/Step/Acceptance/HomeChecker.php

namespace Step\Acceptance;
use Page\Acceotance\HomePage;

class HomeChecker extends \AcceptanceTester
{
    /**
     * @Given The home page
     */
    public function main()
    {
        $homePage = new PageHome($this);

        $this->amOnPage($homePage::URL);
        $this->checkTitle($homePage);
        $this->checkButtons($homePage);
    }

    private function checkTitle($homePage){
        $this->seeInTitle($homePage::$title);
    }

    private function checkButtons($homePage){
        $this->see($homePage::$aButton);
        $this->see($homePage::$bButton);
    }
}

The PageAChecker stepObject :

<?php
// tests/_support/Step/Acceptance/PageAChecker.php

namespace Step\Acceptance;
use Page\PageHome;
use Page\PageA;

class PageAChecker extends \AcceptanceTester
{
    /**
     * @When I click on the button A
     */
    public function clickButton()
    {
        $homePage = new PageHome($this);
        $this->click($homePage::$aButton);
    }

    /**
     * @Then l go to the page A
     */
    public function checkTitle()
    {
        $aPage = new PageA($this);
        $this->seeInTitle($aPage::$title);
    }

}

And the PageBChecker stepObject :

<?php
// tests/_support/Step/Acceptance/PageBChecker.php

namespace Step\Acceptance;
use Page\PageHome;
use Page\PageB;

class PageBChecker extends \AcceptanceTester
{
    /**
     * @When I click on the button B
     */
    public function clickButton()
    {
        $homePage = new PageHome($this);
        $this->click($homePage::$bButton);
    }

    /**
     * @Then l go to the page B
     */
    public function checkTitle()
    {
        $bPage = new PageB($this);
        $this->seeInTitle($bPage::$title);
    }

}

And now, I don't know what I must do. If I empty my AcceptanceTester file and run again the './vendor/bin/codecept run acceptance' command, the test is incomplete and I get "not found in contexts" warnings in my shell :

enter image description here

What do I do?

Update I created a post in the codeception GitHub here :

https://github.com/Codeception/Codeception/issues/5157

I describe a minimal example of reproducing my issue and a (very) ugly resolution. I'm looking to getting a good way and understand why I described does not work!

Nmk
  • 1,281
  • 2
  • 14
  • 25
spacecodeur
  • 2,206
  • 7
  • 35
  • 71
  • Try passing the dependencies as method args, e.g. `function clickButton($homePage PageHome) { $this->click($homePage::$bButton); }`. – Oliver Maksimovic Sep 02 '18 at 05:21
  • Thanks for your help :) I changed like you said, but I get the same output... ("Step definition for `I click on the button B` not found in contexts") – spacecodeur Sep 02 '18 at 08:38
  • I do believe the messages which you set in the /**/ comments above functions are being parsed by your tool. You've put @ notation there, maybe you shouldn't do that? – Anatoliy Kim Sep 07 '18 at 14:57
  • I had theses messages when I created my stackoverflow post :/ for help my saviour haha – spacecodeur Sep 08 '18 at 22:13
  • This seems so complex. Aren't "page objects" and "step objects" essentially the same thing? Both are shorthand notations for something you do over and over again. Combining them just feels like going down the rabit hole. The test should be EASY to understand. Or what's the point of it. – John Dee Sep 24 '18 at 21:57
  • this code does not work if I use only a stepObject Class. Its really a very basic use of codeception : create a feature, create a stepObject Class, add the gherkin:snippets output to the stepObject, and then I have the same issue (context not found when I run) – spacecodeur Sep 25 '18 at 06:49

2 Answers2

1

I get "not found in contexts" warnings in my shell

Ok, how to link gherkin files execution with steps defined in my own contexts classes (PageObjects, StepObjects, ...)? We can read chapter "BDD > Configuration" in Codeception documentation:

As we mentioned earlier, steps should be defined inside context classes. By default all the steps are defined inside an Actor class, for instance, AcceptanceTester. However, you can include more contexts. This can be configured inside global codeception.yml or suite configuration file:

gherkin:
    contexts:
        default:
            - AcceptanceTester
            - AdditionalSteps
            - PageHome
            - HomeChekcer

(...) This way PageObjects, Helpers and StepObjects can become contexts as well.


Better

If we continue reading:

But more preferable to include context classes by their tags or roles.

This means that, bearing in mind escalability and good organization, you will not want to overload every tests with every Page Object. Therefore you can assign Page Object (or any assistant classes) by role, by tag or by paths. See next paragraphs on documentation. Following your example, and assigning by tag:

gherkin:
   contexts:
      default:
         - AcceptanceTester
      tag:
         myTagX:
             - Page\Acceotance\HomePage\HomeChecker
             - Page\PageHome
         anotherTag:
             - Page\Acceotance\another\AnotherChecker
             - Page\PageAnother

...and in gherkin files:

@myTagX
Feature
(...)
Katapofatico
  • 750
  • 1
  • 10
  • 29
0

As I see, You are trying to write your automation layer in a reusable way. You can use the ScreenPlay pattern for it.

the differences between page object and screenplay are discussed here

The key difference is that the screenplay pattern organizes the Page Objects

John Ferguson article: ScreenPlay: the next stage in automated acceptance testing

Mehrdad
  • 1,523
  • 9
  • 23