PageObjectExtension
PageObjectExtension copied to clipboard
Example to make it possible to inject Page objects in symfony 3.4
For several weeks, we've been busy setting up behat in our big 20 year old symfony 3.4 project. During this time we made a bunch of changes to make it simpler to write behat contexts.
This is one of these changes. In order to make it possible to work with symfony autowiring of Page objects, we had to extend SymfonyPage and apply these changes.
The getUrl issue might only exist on our project, but the others should also be a problem for "clean" symfony projects.
The reason I'm opening this issue, is because I'd like to know if you are interested in these kind of changes and if we should make pull requests for something. (We also extended Element to enable us to search for html elements only within that element. Furthermore we added means to interact with Element objects on a Page object.)
<?php
namespace Tweakers\Test\Behat\Page;
use Behat\Mink\Session;
use FriendsOfBehat\PageObjectExtension\Page\SymfonyPage;
use FriendsOfBehat\SymfonyExtension\Mink\MinkParameters;
use Symfony\Component\Routing\RouterInterface;
abstract class BasePage extends SymfonyPage
{
/**
* $minkParameters needs to be type hinted for auto-wiring of Pages to work.
*/
public function __construct(Session $session, MinkParameters $minkParameters, RouterInterface $router)
{
parent::__construct($session, $minkParameters, $router);
}
protected function getUrl(array $urlParameters = []): string
{
// For some reason the url contains the host part twice (e.g. http://domain.com/domain.com/page)
return str_replace(HOST_NAME . '/' . HOST_NAME, HOST_NAME, parent::getUrl($urlParameters));
}
protected function verifyUrl(array $urlParameters = []): void
{
$this->fixSymfonyHost();
parent::verifyUrl($urlParameters);
}
private function fixSymfonyHost(): void
{
$url = $this->getDriver()->getCurrentUrl();
$host = parse_url($url, PHP_URL_HOST);
$this->router->getContext()->setHost($host);
}
}
This change allowed us to write our Domain Contexts like this snippet:
class NewsContext extends FeatureContext
{
/** @var NewsPortalPage */
private $newsPortal;
/** @var NewsArticlePage */
private $articlePage;
/** @var Heading */
private $heading;
public function __construct(
NewsPortalPage $portal,
NewsArticlePage $articlePage,
Heading $heading
) {
$this->newsPortal = $portal;
$this->articlePage = $articlePage;
$this->heading = $heading;
}
/**
* @When I visit the news portal
*/
public function iVisitTheNewsPortal()
{
// try to open, because the news portal redirects to listing of today
$this->newsPortal->tryToOpen();
}
/**
* @When /^I view (this news article)$/
* @When I read the news article :newsArticle
* @Given I am reading the news article :newsArticle
*
* @see NewsArticleTransformer::ensureNewsArticle()
*/
public function iViewThisNewsArticle(NewsArticle $newsArticle)
{
$this->articlePage->open(['id' => $newsArticle->getId()]);
}
/**
* @Then /^I should see (this news article)$/
*
* @see SharedStorageTransformer::getResource()
*/
public function iShouldSeeThisNewsArticle(NewsArticle $article)
{
expect($this->heading)->toHaveText($article->getTitle());
}
}
Hello @winkbrace and thank you for this post! I've been thinking about enabling autowiring in this extension for the last days, but I had no time to do it. I would be happy to accept the PR with a sufficient change, can you open it? We can then discuss functionalities there, as it's much easier when you can refer to a specific line/block of code :) Thanks! 🚀
Hmm I just thought of an alternative. You could also use a transformer to inject the Page object in the Context.
/**
* @When /^I visit (the login page)$/
* @see PageTransformer::createPage()
*/
public function iVisitTheLoginPage(LoginPage $page)
{
$page->open();
}
Then the path to the Page objects must be specified in the config, so the PageTransformer knows where to search