| name | symfony:e2e-panther-playwright |
| description | Write end-to-end tests with Symfony Panther for browser automation or Playwright for complex scenarios |
End-to-End Testing
Symfony Panther
Installation
composer require --dev symfony/panther
Basic Test
<?php
namespace App\Tests\E2E;
use Symfony\Component\Panther\PantherTestCase;
class HomePageTest extends PantherTestCase
{
public function testHomePageLoads(): void
{
$client = static::createPantherClient();
$client->request('GET', '/');
$this->assertSelectorTextContains('h1', 'Welcome');
$this->assertPageTitleContains('Home');
}
}
Form Interaction
public function testContactForm(): void
{
$client = static::createPantherClient();
$crawler = $client->request('GET', '/contact');
$form = $crawler->selectButton('Send')->form([
'contact[name]' => 'John Doe',
'contact[email]' => 'john@example.com',
'contact[message]' => 'Hello!',
]);
$client->submit($form);
$client->waitFor('.alert-success');
$this->assertSelectorTextContains('.alert-success', 'Message sent');
}
JavaScript Interactions
public function testDropdownMenu(): void
{
$client = static::createPantherClient();
$client->request('GET', '/dashboard');
$client->clickLink('User Menu');
$client->waitFor('.dropdown-menu');
$this->assertSelectorIsVisible('.dropdown-menu');
$this->assertSelectorTextContains('.dropdown-menu', 'Profile');
}
Waiting for Elements
public function testAsyncContent(): void
{
$client = static::createPantherClient();
$client->request('GET', '/products');
$client->waitFor('.product-list');
$client->waitForVisibility('.product-card');
$client->waitFor('.slow-content', 10);
$client->waitForInvisibility('.loading-spinner');
$client->waitForElementToContain('.status', 'Complete');
}
Taking Screenshots
public function testProductPage(): void
{
$client = static::createPantherClient();
$client->request('GET', '/products/1');
$client->takeScreenshot('product_page.png');
}
Playwright (Alternative)
For more complex scenarios, use Playwright with its PHP bindings or run separately.
JavaScript Playwright Tests
const { test, expect } = require('@playwright/test');
test('user can login', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('Dashboard');
});
test('login validation', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'invalid');
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toBeVisible();
});
Playwright Configuration
module.exports = {
testDir: './tests/e2e',
timeout: 30000,
use: {
baseURL: 'http://localhost:8000',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
{ name: 'webkit', use: { browserName: 'webkit' } },
],
};
Testing User Flows
Panther: Complete Checkout Flow
public function testCheckoutFlow(): void
{
$client = static::createPantherClient();
$client->request('GET', '/login');
$client->submitForm('Login', [
'email' => 'test@example.com',
'password' => 'password',
]);
$client->waitFor('.dashboard');
$client->clickLink('Products');
$client->waitFor('.product-list');
$client->click('.product-card:first-child .add-to-cart');
$client->waitForElementToContain('.cart-count', '1');
$client->clickLink('Cart');
$client->waitFor('.cart-items');
$client->clickLink();
->();
->(, [
=> ,
=> ,
=> ,
]);
->();
->();
->();
->(, );
}
CI Configuration
GitHub Actions with Panther
name: E2E Tests
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
services:
database:
image: postgres:15
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
extensions: pdo_pgsql
- name: Install Chrome
uses: browser-actions/setup-chrome@latest
- name: Install dependencies
run: composer install
- name: Setup database
run:
Best Practices
- Test critical paths: Login, checkout, signup
- Use explicit waits: Don't rely on implicit timing
- Screenshots on failure: Debug issues quickly
- Separate from unit tests: E2E tests are slow
- Stable selectors: Use data-testid attributes
- Reset state: Clean database between tests