phpunit/phpunit-selenium
PHPUnit-Selenium provides a Selenium2TestCase for running end-to-end browser tests with Selenium 2 in PHPUnit. Install via Composer and use version lines aligned to PHPUnit/PHP (e.g., 9.x for PHPUnit 9 on PHP 7.3+).
Installation:
composer require --dev phpunit/phpunit-selenium
Ensure compatibility with your PHPUnit version (e.g., 9.x for PHPUnit 9.x + PHP 7.3+).
Configure Selenium Server:
selenium-server-standalone-<version>.jar).java -jar selenium-server-standalone-*.jar
Or with Docker:
docker run -d -p 4444:4444 selenium/standalone-firefox
First Test Case:
Extend Selenium2TestCase in your test file:
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverExpectedCondition;
use PHPUnit\Extensions\Selenium2TestCase;
class ExampleTest extends Selenium2TestCase
{
protected function setUp()
{
$host = 'http://localhost:4444/wd/hub';
$this->setBrowserUrl('https://example.com');
$this->setBrowser('firefox');
$this->createSession($host);
}
public function testTitle()
{
$this->url('https://example.com');
$this->assertEquals('Example Domain', $this->getTitle());
}
}
Run Tests:
./vendor/bin/phpunit path/to/ExampleTest
setUp() to initialize the browser session:
protected function setUp()
{
$this->setBrowser('chrome');
$this->createSession('http://localhost:4444/wd/hub');
}
tearDown() to close sessions:
protected function tearDown()
{
$this->quit();
}
Encapsulate interactions in reusable classes:
class LoginPage
{
private $driver;
public function __construct(RemoteWebDriver $driver)
{
$this->driver = $driver;
}
public function login($username, $password)
{
$this->driver->findElement(WebDriverBy::name('username'))->sendKeys($username);
$this->driver->findElement(WebDriverBy::name('password'))->sendKeys($password);
$this->driver->findElement(WebDriverBy::cssSelector('button[type="submit"]'))->click();
}
}
Usage in test:
$loginPage = new LoginPage($this->driver);
$loginPage->login('user', 'pass');
Use Selenium’s built-in waits for flaky elements:
$this->waitUntil(
WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::id('dynamic-element')),
'Element not found'
);
Parameterize tests by browser:
/**
* @dataProvider browserProvider
*/
public function testAcrossBrowsers($browser)
{
$this->setBrowser($browser);
$this->createSession('http://localhost:4444/wd/hub');
// Test logic...
}
public function browserProvider()
{
return [
['firefox'],
['chrome'],
['edge'],
];
}
Service Container Binding: Bind the WebDriver instance to Laravel’s container for dependency injection:
// In a service provider
$this->app->bind(RemoteWebDriver::class, function () {
return new RemoteWebDriver('http://localhost:4444/wd/hub', DesiredCapabilities::firefox());
});
Test Environment:
Use Laravel’s .env for Selenium config:
SELENIUM_HOST=http://selenium:4444/wd/hub
SELENIUM_BROWSER=chrome
Load in setUp():
$this->createSession(config('selenium.host'));
$this->setBrowser(config('selenium.browser'));
Dockerized Selenium: Use Docker Compose for isolated testing:
# docker-compose.yml
services:
selenium:
image: selenium/standalone-chrome
ports:
- "4444:4444"
Start with:
docker-compose up -d
jobs:
test:
runs-on: ubuntu-latest
services:
selenium:
image: selenium/standalone-chrome
ports:
- 4444:4444
steps:
- uses: actions/checkout@v2
- run: composer install
- run: ./vendor/bin/phpunit --testdox-html report.html
Session Management:
$this->quit() in tearDown() can leave zombie sessions.$this->quit() or use a trait to enforce cleanup.Browser Compatibility:
DesiredCapabilities for fine-grained control:
$capabilities = DesiredCapabilities::firefox();
$capabilities->setCapability('marionette', true); // For Firefox GeckoDriver
$this->createSession('http://localhost:4444/wd/hub', $capabilities);
Flaky Tests:
waitUntil) instead of sleep().Headless Mode:
$capabilities = DesiredCapabilities::chrome();
$capabilities->setCapability('goog:chromeOptions', [
'args' => ['headless', 'disable-gpu']
]);
Resource Leaks:
setBrowserUrl() to avoid full page reloads.Logs and Output:
$this->setBrowserLogLevel(\Facebook\WebDriver\WebDriverCapabilities::LOG_DEBUG);
Screenshots on Failure:
Automate screenshot capture in tearDown():
protected function tearDown()
{
if ($this->hasFailed()) {
$this->takeScreenshot('screenshot_' . $this->getName() . '.png');
}
$this->quit();
}
Remote Debugging: Attach Chrome DevTools to a Selenium session:
$capabilities->setCapability('goog:chromeOptions', [
'debuggerAddress' => '127.0.0.1:9222'
]);
Custom Assertions:
Extend Selenium2TestCase to add domain-specific assertions:
class CustomSeleniumTestCase extends Selenium2TestCase
{
protected function assertElementVisible($locator)
{
$this->assertTrue(
$this->isElementVisible($locator),
"Element '$locator' is not visible."
);
}
}
Hooks for CI: Add pre/post-test hooks for CI tools:
class CIAwareTestCase extends Selenium2TestCase
{
protected function setUp()
{
if (getenv('CI')) {
$this->setBrowser('chrome');
$this->createSession('http://selenium:4444/wd/hub');
} else {
// Local setup...
}
}
}
How can I help you explore Laravel packages today?