Playwright is often associated with SPAs — React, Vue, Angular. But it works just as well for classic server-side rendered PHP websites. In my projects, I use Playwright for Symfony sites with dynamic JavaScript components, multilingual navigation, and form validation.
Why Playwright Instead of Cypress or Selenium?
- Multi-browser: Chromium, Firefox, WebKit in a single framework
- Mobile viewports: iPhone, Pixel — viewport and user-agent emulation
- Auto-waiting: Automatically waits for DOM elements and network requests
- TypeScript-native: First-class TypeScript support without workarounds
Configuration for Symfony
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env.BASE_URL
|| 'https://mein-projekt.ddev.site',
ignoreHTTPSErrors: true,
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
},
{
name: 'mobile',
use: { ...devices['iPhone 13'] }
},
],
});
Basic Page Tests
import { test, expect } from '@playwright/test';
test('homepage loads and shows hero section', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Wunner Software/);
await expect(page.locator('.hero h1')).toBeVisible();
});
test('contact form renders all fields', async ({ page }) => {
await page.goto('/kontakt');
await expect(page.locator('input[name="contact[name]"]')).toBeVisible();
await expect(page.locator('input[name="contact[mail]"]')).toBeVisible();
await expect(page.locator('textarea[name="contact[message]"]')).toBeVisible();
});
Testing Multilingual Navigation
For bilingual sites (DE/EN), I systematically test the language switch:
test('language switch DE to EN works', async ({ page }) => {
await page.goto('/');
await expect(page.locator('html')).toHaveAttribute('lang', 'de');
// Click on English flag
await page.locator('.lang-switch a[title="English"]').click();
await expect(page.locator('html')).toHaveAttribute('lang', 'en');
await expect(page).toHaveURL(/\/en/);
});
test('all pages return 200', async ({ page }) => {
const routes = [
'/', '/en',
'/kontakt', '/en/contact',
'/blog', '/en/blog',
'/impressum', '/en/imprint',
'/datenschutz', '/en/privacy',
];
for (const route of routes) {
const response = await page.goto(route);
expect(response?.status()).toBe(200);
}
});
Testing Mobile Navigation
On mobile viewports, the navigation is collapsed. The most common mistake: testing links that are hidden behind the hamburger menu.
test('mobile: navigation links work', async ({ page, isMobile }) => {
await page.goto('/');
if (isMobile) {
// Open hamburger menu
await page.locator('.navbar-toggler').click();
await page.waitForSelector('.navbar-collapse.show');
}
const blogLink = page.locator('.nav-link', { hasText: 'Blog' });
await expect(blogLink).toBeVisible();
await blogLink.click();
await expect(page).toHaveURL(/blog/);
});
Testing Async Components
When JavaScript components load data via API, Playwright needs to wait for the loading to complete:
test('blog search shows suggestions', async ({ page }) => {
await page.goto('/blog');
const searchInput = page.locator('#blog-search-input');
await searchInput.fill('Symfony');
// Wait for debounce (800ms) + API response
await page.waitForSelector('.blog-search-suggestion-item', {
timeout: 5000
});
const suggestions = page.locator('.blog-search-suggestion-item');
await expect(suggestions.first()).toBeVisible();
});
Testing Forms with CAPTCHA
ALTCHA CAPTCHAs cannot be solved in tests. The solution: In the test environment, CAPTCHA validation is skipped.
# services_test.yaml
services:
App\Validator\AltchaChallengeValidator:
arguments:
$enabled: false
The Playwright test can then run through the form completely:
test('contact form submission works', async ({ page }) => {
await page.goto('/kontakt');
await page.fill('input[name="contact[name]"]', 'Test User');
await page.fill('input[name="contact[mail]"]', 'test@example.com');
await page.fill('textarea[name="contact[message]"]', 'Test message');
await page.click('button[type="submit"]');
// Redirect to success page
await expect(page).toHaveURL(/erfolg|success/);
});
CI Integration
In the CI pipeline, Playwright runs headless. Important: Preserve screenshots and traces on failures:
- name: Run Playwright tests
run: npx playwright test
env:
BASE_URL: https://localhost
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
Conclusion
Playwright is just as capable for PHP websites as it is for SPAs. The auto-wait mechanism, multi-browser support, and TypeScript integration make it the ideal tool for E2E tests — regardless of the backend stack.
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.