Playwright wird oft mit SPAs assoziiert — React, Vue, Angular. Aber es eignet sich genauso hervorragend für klassische serverseitig gerenderte PHP-Webseiten. In meinen Projekten nutze ich Playwright für Symfony-Seiten mit dynamischen JavaScript-Komponenten, mehrsprachiger Navigation und Formularvalidierung.
Warum Playwright statt Cypress oder Selenium?
- Multi-Browser: Chromium, Firefox, WebKit in einem Framework
- Mobile Viewports: iPhone, Pixel — Viewport- und User-Agent-Emulation
- Auto-Waiting: Wartet automatisch auf DOM-Elemente und Netzwerk-Requests
- TypeScript-native: Erstklassiger TypeScript-Support ohne Workarounds
Konfiguration für 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'] }
},
],
});
Grundlegende Seitentests
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();
});
Mehrsprachige Navigation testen
Für zweisprachige Seiten (DE/EN) teste ich den Sprachwechsel systematisch:
test('language switch DE to EN works', async ({ page }) => {
await page.goto('/');
await expect(page.locator('html')).toHaveAttribute('lang', 'de');
// Auf englische Flagge klicken
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);
}
});
Mobile Navigation testen
Auf mobilen Viewports ist die Navigation eingeklappt. Häufigster Fehler: Man testet Links, die hinter dem Hamburger-Menü versteckt sind.
test('mobile: navigation links work', async ({ page, isMobile }) => {
await page.goto('/');
if (isMobile) {
// Hamburger-Menü öffnen
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/);
});
Asynchrone Komponenten testen
Wenn JavaScript-Komponenten Daten per API laden, muss Playwright auf das Laden warten:
test('blog search shows suggestions', async ({ page }) => {
await page.goto('/blog');
const searchInput = page.locator('#blog-search-input');
await searchInput.fill('Symfony');
// Auf Debounce (800ms) + API-Response warten
await page.waitForSelector('.blog-search-suggestion-item', {
timeout: 5000
});
const suggestions = page.locator('.blog-search-suggestion-item');
await expect(suggestions.first()).toBeVisible();
});
Formulare mit CAPTCHA testen
ALTCHA-CAPTCHAs können in Tests nicht gelöst werden. Die Lösung: In der Testumgebung wird die CAPTCHA-Validierung übersprungen.
# services_test.yaml
services:
App\Validator\AltchaChallengeValidator:
arguments:
$enabled: false
Der Playwright-Test kann dann das Formular komplett durchlaufen:
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 zur Erfolgsseite
await expect(page).toHaveURL(/erfolg|success/);
});
CI-Integration
In der CI-Pipeline läuft Playwright headless. Wichtig: Screenshots und Traces bei Fehlern aufbewahren:
- 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
Fazit
Playwright ist für PHP-Webseiten genauso leistungsfähig wie für SPAs. Der Auto-Wait-Mechanismus, die Multi-Browser-Unterstützung und die TypeScript-Integration machen es zum idealen Tool für E2E-Tests — unabhängig vom Backend-Stack.
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.