Working with page objects¶
Creating a page object class¶
To create a new page object extend the
SensioLabs\Behat\PageObjectExtension\PageObject\Page
class:
<?php namespace Page; use SensioLabs\Behat\PageObjectExtension\PageObject\Page; class Homepage extends Page { }
Instantiating a page object¶
Injecting page objects into a context file¶
Page objects will be injected directly into a context file if they’re defined as constructor arguments with a type hint:
<?php use Behat\Behat\Context\Context; use Page\Homepage; use Page\Element\Navigation; class SearchContext implements Context { private $homepage; private $navigation; public function __construct(Homepage $homepage, Navigation $navigation) { $this->homepage = $homepage; $this->navigation = $navigation; } /** * @Given /^(?:|I )visited homepage$/ */ public function iVisitedThePage() { $this->homepage->open(); } }
Using the page object factory¶
Pages are created with a built in factory. One of the ways to use them in your
context is to call the getPage
method provided by the
SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext
:
<?php use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext; class SearchContext extends PageObjectContext { /** * @Given /^(?:|I )search for (?P<keywords>.*?)$/ */ public function iSearchFor($keywords) { $this->getPage('Homepage')->search($keywords); } }Note
Alternatively you could implement the
SensioLabs\Behat\PageObjectExtension\Context\PageObjectAwareInterface
.
Page factory finds a corresponding class by the passed name:
- “Homepage” becomes a “Homepage” class
- “Article list” becomes an “ArticleList” class
- “My awesome page” becomes a “MyAwesomePage” class
From version 2.1 it is possibile to use getPage()
method with page FQCN as follows
<?php use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext; use Page\Homepage; class SearchContext extends PageObjectContext { /** * @Given /^(?:|I )search for (?P<keywords>.*?)$/ */ public function iSearchFor($keywords) { $this->getPage(Homepage::class)->search($keywords); } }
If you choose FQCN strategy, you can organize your page directories freely as you are not bounded to page namespace (see Configuration)
Note
You can choose between “CamelCase” strategy and “FQCN” strategy. We recommend to keep a consistent strategy for the factory but there is not any constraint: both strategies can work togheter with their own rules.
Note
It is possible to implement your own way of mapping a page name to an appropriate page object with a custom factory.
Opening a page¶
Page can be opened by calling the open()
method:
<?php use Behat\Behat\Context\Context; use SensioLabs\Behat\PageObjectExtension\Context\PageObjectContext; class SearchContext implements Context { private $homepage; private $navigation; public function __construct(Homepage $homepage, Navigation $navigation) { $this->homepage = $homepage; $this->navigation = $navigation; } /** * @Given /^(?:|I )visited (?:|the )(?P<pageName>.*?)$/ */ public function iVisitedThePage($pageName) { if (!isset($this->$pageName)) { throw new \RuntimeException(sprintf('Unrecognised page: "%s".', $pageName)); } $this->$pageName->open(); } }
However, to be able to do this we have to provide a $path
property:
<?php namespace Page; use SensioLabs\Behat\PageObjectExtension\PageObject\Page; class Homepage extends Page { /** * @var string $path */ protected $path = '/'; }Note
$path
represents an URL of your page. You can omit the$path
if your page object is only returned from other pages and you’re not planning on opening it directly.$path
is only used if you callopen()
on the page.
Path can also be parametrised:
protected $path = '/employees/{employeeId}/messages';
Any parameters should be given to the open()
method:
$this->getPage($pageName)->open(array('employeeId' => 13));
It’s also possible to check if a given page is opened with isOpen()
method:
$isOpen = $this->getPage($pageName)->isOpen(array('employeeId' => 13));
Both open()
and isOpen()
run the same verifications, which can be overriden:
verifyResponse()
- verifies if the response was successful. It only works for drivers which support getting a response status code.verifyUrl()
- verifies if the current URL matches the expected one. The default implementation only checks if a page url is exactly the same as the current url. Override this method to implement your custom matching logic. The method should throw an exception in case URLs don’t match.verifyPage()
- verifies if the page content matches the expected content. It is up to you to implement the logic here. The method should throw an exception in case the content expected to be present on the page is not there.
Implementing page objects¶
Page is an instance of a Mink
DocumentElement.
This means that instead of accessing Mink
or Session
objects, we can take
advantage of existing Mink Element methods:
<?php namespace Page; use Behat\Mink\Exception\ElementNotFoundException; use SensioLabs\Behat\PageObjectExtension\PageObject\Page; class Homepage extends Page { // ... /** * @param string $keywords * * @return Page */ public function search($keywords) { $searchForm = $this->find('css', 'form#search'); if (!$searchForm) { throw new ElementNotFoundException($this->getDriver(), 'form', 'css', 'form#search'); } $searchForm->fillField('q', $keywords); $searchForm->pressButton('Google Search'); return $this->getPage('Search results'); } }
Notice that after clicking the Search button we’ll be redirected to a search results
page. Our method reflects this intent and returns another page by creating it with
a getPage()
helper method first.
Pages are created with the same factory which is used in the context files.
Reference the official Mink API documentation for a full list of available methods:
Note that when using page objects, the context files are only responsible for calling methods on the page objects and making assertions. It’s important to make this separation and avoid assertions in the page objects in general.
Page objects should either return other page objects or provide ways to access attributes of a page (like a title).