friends-of-behat/page-object-extension
composer require friends-of-behat/page-object-extension --dev
behat.yml:
extensions:
FriendsOfBehat\PageObjectExtension:
page_classes_prefix: 'App\Tests\Behat\Page'
element_classes_prefix: 'App\Tests\Behat\Element'
src/Tests/Behat/Page/LoginPage.php):
<?php
namespace App\Tests\Behat\Page;
use FriendsOfBehat\PageObjectExtension\Page\Page;
class LoginPage extends Page
{
public function getUrl(): string
{
return '/login';
}
public function getEmailField()
{
return $this->findField('email');
}
}
use App\Tests\Behat\Page\LoginPage;
/** @When I am on the login page */
public function iAmOnTheLoginPage(LoginPage $page)
{
$page->open();
}
Page or SymfonyPage for Symfony apps.Element for reusable components (e.g., modals, forms).Page for full-page interactions (e.g., DashboardPage).Element for reusable components (e.g., SidebarElement, SearchBarElement).// SidebarElement.php
use FriendsOfBehat\PageObjectExtension\Element\Element;
class SidebarElement extends Element
{
public function clickMenuItem(string $name)
{
$this->findLink($name)->click();
}
}
SymfonyPage for Symfony apps to leverage kernel/container:
use FriendsOfBehat\PageObjectExtension\Page\SymfonyPage;
class AdminPage extends SymfonyPage
{
public function getUrl(): string
{
return $this->getRouter()->generate('admin_dashboard');
}
}
/** @When I click :button on the :page */
public function iClickButtonOnPage(Page $page, string $button)
{
$page->findButton($button)->click();
}
HeaderElement) and inject them into multiple pages.class ProductPage extends Page
{
public function __construct(private string $slug)
{
}
public function getUrl(): string
{
return "/products/{$this->slug}";
}
}
Page to add domain-specific assertions:
class CheckoutPage extends Page
{
public function assertOrderTotal(float $expected): void
{
$actual = $this->find('css', '.order-total')->getText();
Assert::assertEquals($expected, $actual);
}
}
SymfonyPage to generate URLs via Symfony’s router:
$url = $this->getRouter()->generate('app_product_show', ['slug' => 'test']);
page_classes_prefix and element_classes_prefix in behat.yml match your autoload paths.Tests/Behat/Page, set:
FriendsOfBehat\PageObjectExtension:
page_classes_prefix: 'Tests\Behat\Page'
id="user-email"). Prefer CSS or XPath with classes/attributes.$this->find('css', '.user-email-input')->sendKeys('test@example.com');
SymfonyPage, ensure your AppKernel or KernelInterface is bootstrapped. Debug with:
var_dump($this->getKernel()->getContainer()->has('router'));
HeaderElement into Page).->waitForElementVisible() or ->waitForElement() to handle dynamic content:
$this->find('css', '.dynamic-element')->waitForElementVisible(10);
var_dump($this->getPageSource()); // Dump HTML for inspection
$this->assertElementVisible('css', '.loader', false); // Fail fast
SymfonyPage fails to resolve services, ensure your BehatContext extends SymfonyContext or has the kernel injected:
use FriendsOfBehat\PageObjectExtension\Context\SymfonyContext;
class FeatureContext extends SymfonyContext
{
// ...
}
Element for domain-specific behaviors:
class DataTableElement extends Element
{
public function getRowCount(): int
{
return $this->findAll('css', 'tr')->count();
}
}
open() or close() for pre/post-actions:
class LoginPage extends Page
{
public function open(): void
{
$this->getSession()->start();
parent::open();
}
}
Behat\MinkExtension for custom drivers or Behat\SymfonyExtension for Symfony-specific features.Mink in Element:
$this->getSession()->getPage()->find('link', 'Sign Up');
Page for reusable checks:
class BasePage extends Page
{
public static function assertPageTitle(Page $page, string $expected)
{
Assert::assertEquals($expected, $page->getTitle());
}
}
How can I help you explore Laravel packages today?