friends-of-behat/page-object-extension
composer require --dev friends-of-behat/page-object-extension behat/behat
behat.yml):
default:
extensions:
FriendsOfBehat\PageObjectExtension:
alias: PageObject
src/Tests/Behat/Page/HomePage.php):
namespace Tests\Behat\Page;
use FriendsOfBehat\PageObjectExtension\Page\Page;
class HomePage extends Page
{
public static $url = '/';
public function getTitle(): string
{
return $this->getDriver()->getTitle();
}
}
src/Tests/Behat/Context/FeatureContext.php):
use FriendsOfBehat\PageObjectExtension\Page\Page;
use Behat\Behat\Context\Context;
use Tests\Behat\Page\HomePage;
class FeatureContext implements Context
{
private HomePage $homePage;
public function __construct(HomePage $homePage)
{
$this->homePage = $homePage;
}
/**
* @When I visit the homepage
*/
public function iVisitTheHomepage()
{
$this->homePage->open();
}
/**
* @Then I should see the title :title
*/
public function iShouldSeeTheTitle(string $title)
{
expect($this->homePage->getTitle())->toBe($title);
}
}
vendor/bin/behat
Scenario: Test a simple homepage title.
File: features/homepage.feature
Feature: Homepage
As a visitor
I want to see the correct title
So I know I'm on the right site
Scenario: Homepage displays correct title
Given I am on the homepage
Then I should see the title "Welcome to Our Site"
DashboardPage, CheckoutPage).
class DashboardPage extends Page
{
public static $url = '/dashboard';
public $title = 'Dashboard';
public function getWelcomeMessage(): string
{
return $this->getDriver()->find('css', '.welcome-message')->getText();
}
}
class LoginForm extends Element
{
public function fillEmail(string $email): void
{
$this->fillField('email', $email);
}
public function fillPassword(string $password): void
{
$this->fillField('password', $password);
}
}
class LoginPage extends Page
{
public static $url = '/login';
public LoginForm $form;
public function __construct()
{
$this->form = new LoginForm($this->getDriver());
}
}
Use SymfonyPage as a base for Laravel-specific pages by overriding Symfony dependencies:
class LaravelPage extends SymfonyPage
{
protected function getUrl(): string
{
return route($this->getRouteName());
}
protected function getParameter(string $name)
{
return app($name); // Laravel's container
}
}
Leverage Laravel’s service container to inject dependencies:
class FeatureContext
{
public function __construct(
HomePage $homePage,
AuthService $authService // Laravel service
) {
$this->homePage = $homePage;
$this->authService = $authService;
}
}
Use methods to generate selectors dynamically:
class UserProfilePage extends Page
{
public function getUserNameField(): Element\Field
{
return $this->find('css', '.user-name', Element\Field::class);
}
}
Extract common steps into context methods:
class CommonStepsContext
{
/**
* @Given I am logged in as :user
*/
public function iAmLoggedInAs(string $user)
{
$this->authService->login($user);
}
}
Laravel-Specific URL Handling:
Override getUrl() to use Laravel’s route() helper:
class AdminPage extends LaravelPage
{
protected function getRouteName(): string
{
return 'admin.dashboard';
}
}
Element Locators: Use Laravel’s Blade directives or custom logic for dynamic IDs:
public function getDynamicElement(): Element\Button
{
return $this->find('css', '.btn[data-id="' . $this->getDynamicId() . '"]', Element\Button::class);
}
Test Data Setup: Use Laravel factories or seeders to populate test data:
/**
* @BeforeScenario
*/
public function createTestUser()
{
User::factory()->create(['email' => 'test@example.com']);
}
Parallel Testing: Configure Behat for parallel execution:
# behat.yml
default:
extensions:
Behat\MinkExtension:
base_url: 'http://localhost'
FriendsOfBehat\PageObjectExtension:
alias: PageObject
Behat\ParallelExtension:
workers: 4
CI/CD Optimization: Cache dependencies and use Laravel’s queue workers for background jobs during tests.
Symfony Dependencies in Laravel:
SymfonyPage relies on Symfony’s ContainerInterface. Laravel’s container is compatible but may throw errors for Symfony-specific methods.SymfonyPage and override unsupported methods:
class LaravelPage extends SymfonyPage
{
protected function getParameter(string $name)
{
return app($name); // Laravel's container
}
}
Route Resolution:
route() helper may not work in Behat’s context if the app isn’t bootstrapped.UrlGenerator:
$url = app('url')->route('admin.dashboard');
Driver Initialization:
behat.yml:
default:
extensions:
Behat\MinkExtension:
base_url: 'http://localhost'
sessions:
default:
selenium2: ~
Static $url Conflicts:
$url in pages may break if routes change.class DynamicPage extends Page
{
public static function getUrl(): string
{
return route('dynamic.route', ['param' => 'value']);
}
}
Element Not Found:
public function assertElementExists(): void
{
$this->waitFor(5)->until(
fn() => $this->getDriver()->find('css', '.element') !== null
);
}
Dependency Injection in Contexts:
setService() or bind services in behat.yml:
default:
extensions:
FriendsOfBehat\PageObjectExtension:
alias: PageObject
services:
auth_service: '@authService' # Laravel service
Enable Mink Debugging:
# behat.yml
default:
extensions:
Behat\MinkExtension:
debug: true
Log Page/Element Interactions: Add debug methods to pages/elements:
public function debugElement(string $selector): void
{
$element = $this->getDriver()->find('css', $selector);
\Behat\Behat\Hook\Scope\BeforeScenarioScope::log("Element found: " . (bool) $element);
}
Use Behat’s --verbose Flag:
vendor/bin/behat
How can I help you explore Laravel packages today?