Playwright Tests in Drupal 11: Modernes End-to-End Testing
Playwright Tests in Drupal 11: Der ultimative Guide
End-to-End Testing ist in modernen Drupal-Projekten unverzichtbar geworden. Während PHPUnit und Drupal’s FunctionalJavascript Tests ihre Berechtigung haben, bietet Playwright eine moderne, schnelle und developer-friendly Alternative für umfassende Browser-Tests.
Was ist Playwright?
Playwright ist ein Open-Source Framework von Microsoft für zuverlässige End-to-End Tests. Im Gegensatz zu Selenium bietet Playwright:
- Auto-Waiting: Keine manuellen
sleep()oderwaitFor()Calls nötig - Multi-Browser Support: Chromium, Firefox, WebKit mit einer API
- Blazing Fast: Headless Tests in Sekunden statt Minuten
- Developer Tools: Test Generator, Inspector, Trace Viewer
- Native Mobile Testing: iPhone, iPad, Android Emulation
Für Drupal 11 bedeutet das: Tests für komplexe Editorial Workflows, Layout Builder Konfigurationen oder Webform-Submissions laufen stabil und schnell.
Installation in Drupal 11 Projekten
Voraussetzungen
# Node.js 18+ erforderlich
node --version # v18.0.0 oder höher
# Drupal 11 Installation mit Composer
composer require drupal/core:^11.0
Playwright Setup
# Im Drupal-Root oder separatem tests/ Verzeichnis
npm init playwright@latest
# Installation mit Prompts:
# - TypeScript (empfohlen)
# - tests/ als Testverzeichnis
# - GitHub Actions workflow (optional)
Dies erstellt folgende Struktur:
tests/
|-- playwright.config.ts # Hauptkonfiguration
|-- example.spec.ts # Beispieltest
\-- fixtures/ # Custom Fixtures
package.json
playwright.config.ts
Drupal-spezifische Konfiguration
Passe playwright.config.ts an:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
// Drupal ist oft langsamer - längere Timeouts
timeout: 30 * 1000,
expect: {
timeout: 10000
},
// Screenshots bei Fehlern
use: {
baseURL: 'http://drupal11.ddev.site', // Deine lokale URL
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'retain-on-failure',
},
// Parallele Tests
workers: process.env.CI ? 2 : 4,
// Browser-Matrix
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'mobile',
use: { ...devices['iPhone 13'] },
},
],
// Dev Server (optional)
webServer: {
command: 'ddev start',
url: 'http://drupal11.ddev.site',
reuseExistingServer: !process.env.CI,
},
});
Dein erster Drupal Test
Einfacher Login-Test
// tests/auth/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Drupal Login', () => {
test('Admin kann sich einloggen', async ({ page }) => {
// Zur Login-Seite navigieren
await page.goto('/user/login');
// Formular ausfüllen
await page.fill('#edit-name', 'admin');
await page.fill('#edit-pass', 'admin123');
// Login-Button klicken
await page.click('#edit-submit');
// Erfolgreiche Weiterleitung prüfen
await expect(page).toHaveURL('/admin/content');
// Admin Toolbar sichtbar?
await expect(page.locator('#toolbar-administration')).toBeVisible();
// Willkommensnachricht prüfen
await expect(page.locator('.messages--status'))
.toContainText('You have logged in');
});
test('Falsches Passwort zeigt Fehlermeldung', async ({ page }) => {
await page.goto('/user/login');
await page.fill('#edit-name', 'admin');
await page.fill('#edit-pass', 'wrong-password');
await page.click('#edit-submit');
// Fehlermeldung erscheint
await expect(page.locator('.messages--error'))
.toContainText('Unrecognized username or password');
// Noch auf Login-Seite
await expect(page).toHaveURL(/.*user\/login/);
});
});
Test ausführen
# Alle Tests
npx playwright test
# Spezifischer Test
npx playwright test login.spec.ts
# Mit UI (Debug Mode)
npx playwright test --ui
# In einem Browser
npx playwright test --headed --project=chromium
Fortgeschrittene Drupal Test-Patterns
1. Authentication Fixtures
Vermeide wiederholte Logins mit State-Reuse:
// tests/fixtures/auth.fixture.ts
import { test as base } from '@playwright/test';
export const test = base.extend({
authenticatedPage: async ({ page }, use) => {
// Login einmal durchführen
await page.goto('/user/login');
await page.fill('#edit-name', 'admin');
await page.fill('#edit-pass', 'admin123');
await page.click('#edit-submit');
// Session speichern
await page.context().storageState({
path: 'tests/.auth/admin.json'
});
await use(page);
},
});
// Verwendung:
test('Create Article', async ({ authenticatedPage }) => {
await authenticatedPage.goto('/node/add/article');
// ... bereits eingeloggt!
});
2. Content Creation Tests
// tests/content/article.spec.ts
test('Create and Publish Article', async ({ authenticatedPage: page }) => {
await page.goto('/node/add/article');
// Titel eingeben
await page.fill('#edit-title-0-value', 'Mein Playwright Test-Artikel');
// Body mit CKEditor
const editorFrame = page.frameLocator('.cke_wysiwyg_frame');
await editorFrame.locator('body').fill(
'Dies ist der Inhalt meines Test-Artikels.'
);
// Taxonomie-Tag hinzufügen
await page.fill('#edit-field-tags-0-target-id', 'Testing');
// Bild hochladen
await page.setInputFiles(
'#edit-field-image-0-upload',
'tests/fixtures/test-image.jpg'
);
await page.waitForSelector('.image-widget', { state: 'visible' });
// Als veröffentlicht markieren
await page.check('#edit-status-value');
// Speichern
await page.click('#edit-submit');
// Erfolgsmeldung prüfen
await expect(page.locator('.messages--status'))
.toContainText('Article Mein Playwright Test-Artikel has been created');
// Artikel erscheint auf Frontpage?
await page.goto('/');
await expect(page.locator('.node--type-article'))
.toContainText('Mein Playwright Test-Artikel');
});
3. Layout Builder Tests
test('Layout Builder: Add Block', async ({ authenticatedPage: page }) => {
await page.goto('/admin/structure/types/manage/page/display');
// Layout Builder aktivieren
await page.check('#edit-layout-enabled');
await page.click('#edit-submit');
// Test-Page erstellen
await page.goto('/node/add/page');
await page.fill('#edit-title-0-value', 'Layout Test Page');
await page.click('#edit-submit');
// Layout bearbeiten
await page.click('a:has-text("Layout")');
await page.click('a:has-text("Add block")');
// Block hinzufügen
await page.click('a:has-text("Custom block")');
await page.fill('#edit-settings-label', 'Test Block');
await page.fill('#edit-settings-body-value', 'Block Content');
await page.click('button:has-text("Add block")');
// Layout speichern
await page.click('button:has-text("Save layout")');
// Block ist sichtbar
await expect(page.locator('.block-inline-block'))
.toContainText('Block Content');
});
4. Webform Submission Tests
test('Webform: Contact Form Submission', async ({ page }) => {
await page.goto('/contact');
// Formular ausfüllen
await page.fill('#edit-name', 'Max Mustermann');
await page.fill('#edit-email', 'max@example.com');
await page.fill('#edit-subject-0-value', 'Test Anfrage');
await page.fill('#edit-message-0-value', 'Dies ist eine Test-Nachricht.');
// Honeypot sollte leer bleiben
await expect(page.locator('#edit-website')).toBeHidden();
// Absenden
await page.click('#edit-submit');
// Bestätigung
await expect(page.locator('.messages--status'))
.toContainText('Your message has been sent');
// In DB geprüft (via API)
const response = await page.request.get('/api/webform-submissions/contact');
expect(response.ok()).toBeTruthy();
});
Performance Testing mit Playwright
test('Homepage Performance', async ({ page }) => {
// Performance Metrics sammeln
await page.goto('/', { waitUntil: 'networkidle' });
const metrics = await page.evaluate(() => {
const navigation = performance.getEntriesByType('navigation')[0];
return {
domContentLoaded: navigation.domContentLoadedEventEnd,
loadComplete: navigation.loadEventEnd,
firstPaint: performance.getEntriesByType('paint')[0]?.startTime,
};
});
// Assertions
expect(metrics.domContentLoaded).toBeLessThan(3000); // 3s
expect(metrics.loadComplete).toBeLessThan(5000); // 5s
// Lighthouse Audit (mit Playwright-Lighthouse)
const { playAudit } = require('playwright-lighthouse');
await playAudit({
page,
thresholds: {
performance: 80,
accessibility: 90,
'best-practices': 85,
seo: 90,
},
});
});
Visual Regression Testing
test('Homepage Visual Regression', async ({ page }) => {
await page.goto('/');
// Screenshot vergleichen
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
maxDiffPixels: 100,
});
});
test('Responsive Design Check', async ({ page }) => {
await page.goto('/');
// Desktop
await page.setViewportSize({ width: 1920, height: 1080 });
await expect(page).toHaveScreenshot('homepage-desktop.png');
// Tablet
await page.setViewportSize({ width: 768, height: 1024 });
await expect(page).toHaveScreenshot('homepage-tablet.png');
// Mobile
await page.setViewportSize({ width: 375, height: 667 });
await expect(page).toHaveScreenshot('homepage-mobile.png');
});
CI/CD Integration
GitHub Actions
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Setup Drupal
run: |
composer install
./vendor/bin/drush site:install -y
./vendor/bin/drush config:import -y
- name: Run Playwright Tests
run: npx playwright test
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 30
GitLab CI
# .gitlab-ci.yml
playwright:
image: mcr.microsoft.com/playwright:v1.40.0-focal
script:
- npm ci
- npx playwright install
- composer install
- ./vendor/bin/drush site:install -y
- npx playwright test
artifacts:
when: always
paths:
- playwright-report/
expire_in: 1 week
Best Practices für Drupal + Playwright
1. Nutze Data Attributes für Selectors
// [Fragil] - ändert sich mit Theme-Updates
await page.click('.button.button--primary.form-submit');
// [Stabil] - nutze data-testid
await page.click('[data-testid="submit-article"]');
In Drupal Twig Templates:
<button
type="submit"
data-testid="submit-article"
class="{{ button_classes }}">
{{ 'Submit'|t }}
</button>
2. Page Object Pattern
// tests/pages/ArticlePage.ts
export class ArticlePage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/node/add/article');
}
async fillTitle(title: string) {
await this.page.fill('#edit-title-0-value', title);
}
async fillBody(content: string) {
const frame = this.page.frameLocator('.cke_wysiwyg_frame');
await frame.locator('body').fill(content);
}
async submit() {
await this.page.click('#edit-submit');
}
async expectSuccess() {
await expect(this.page.locator('.messages--status'))
.toContainText('has been created');
}
}
// Verwendung:
test('Create Article with POM', async ({ authenticatedPage }) => {
const article = new ArticlePage(authenticatedPage);
await article.goto();
await article.fillTitle('Test Artikel');
await article.fillBody('Test Content');
await article.submit();
await article.expectSuccess();
});
3. Schnellere Tests mit Drush
// tests/fixtures/drupal.fixture.ts
import { test as base } from '@playwright/test';
import { execSync } from 'child_process';
export const test = base.extend({
authenticatedPage: async ({ page }, use) => {
// Login via Drush statt Browser
const sessionCookie = execSync(
'./vendor/bin/drush user:login --uid=1 --uri=http://drupal11.ddev.site'
).toString().match(/session=([^&]+)/)?.[1];
await page.context().addCookies([
{
name: 'SSESS[...]',
value: sessionCookie,
domain: 'drupal11.ddev.site',
path: '/',
}
]);
await use(page);
},
});
4. Test Data Management
// tests/fixtures/test-data.ts
export async function createTestArticle(page: Page) {
const response = await page.request.post('/jsonapi/node/article', {
data: {
data: {
type: 'node--article',
attributes: {
title: 'Test Article',
body: { value: 'Test content', format: 'basic_html' },
status: true,
},
},
},
headers: {
'Content-Type': 'application/vnd.api+json',
'X-CSRF-Token': await getCSRFToken(page),
},
});
return await response.json();
}
// Cleanup nach Tests
test.afterEach(async ({ page }) => {
await page.request.delete('/jsonapi/node/article/TEST_UUID');
});
5. Accessibility Testing
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('Homepage meets WCAG 2.1 Level AA', async ({ page }) => {
await page.goto('/');
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});
Debugging Playwright Tests
1. UI Mode (interaktiv)
npx playwright test --ui
- Time Travel Debugging
- Watch Mode
- DOM Snapshots
- Network Inspector
2. Headed Mode (Browser sichtbar)
npx playwright test --headed --project=chromium
3. Debug Mode mit Breakpoints
test('Debug Example', async ({ page }) => {
await page.goto('/user/login');
// Browser stoppt hier
await page.pause();
await page.fill('#edit-name', 'admin');
// ... weiter
});
4. Trace Viewer
# Test mit Trace aufnehmen
npx playwright test --trace on
# Trace öffnen
npx playwright show-trace trace.zip
Zeigt:
- DOM Snapshots bei jedem Step
- Console Logs
- Network Requests
- Screenshots
- Timeline
5. Screenshots & Videos
test('Debug mit Screenshots', async ({ page }) => {
await page.goto('/admin/content');
// Manueller Screenshot
await page.screenshot({ path: 'debug-screenshot.png' });
// Element-spezifisch
await page.locator('.view-content').screenshot({
path: 'content-list.png'
});
});
Vergleich: Playwright vs. Drupal FunctionalJavascript
| Feature | Playwright | Drupal FunctionalJavascript |
|---|---|---|
| Speed | Sehr schnell (Headless) | Langsamer (Symfony BrowserKit) |
| Browser | Chromium, Firefox, WebKit | Chrome/Firefox via WebDriver |
| Auto-Waiting | Ja (Eingebaut) | Nein (Manuell mit assertSession()->waitFor()) |
| Debugging | UI Mode, Trace Viewer, Inspector | --verbose, dump(), Breakpoints |
| CI/CD | Docker Images verfügbar | Drupal Site + Webdriver Setup nötig |
| Setup | npm init playwright | Teil von Drupal Core |
| Test Language | TypeScript/JavaScript | PHP |
| Parallel Tests | Ja (Out-of-the-box) | Bedingt (Mit Test Traits möglich) |
| Mobile Testing | Ja (Device Emulation) | Nein (Nicht unterstützt) |
| Best for | User Journeys, E2E, Visual Tests | Drupal-spezifische Integration |
Empfehlung: Nutze beide komplementär:
- FunctionalJavascript: Drupal Core/Module Funktionen
- Playwright: Kritische User Workflows, Cross-Browser, Performance
Typische Drupal Use Cases
1. Content Moderation Workflow
test('Article Draft -> Review -> Published', async ({ page }) => {
// Als Editor einloggen
await loginAs(page, 'editor');
// Draft erstellen
await page.goto('/node/add/article');
await page.fill('#edit-title-0-value', 'Review Me');
await page.selectOption('#edit-moderation-state-0-state', 'draft');
await page.click('#edit-submit');
// Als Reviewer einloggen
await loginAs(page, 'reviewer');
await page.goto('/admin/content/moderated');
await page.click('a:has-text("Review Me")');
// In Review setzen
await page.click('a:has-text("Edit")');
await page.selectOption('#edit-moderation-state-0-state', 'in_review');
await page.click('#edit-submit');
// Als Publisher freigeben
await loginAs(page, 'publisher');
await page.goto('/admin/content/moderated');
await page.click('a:has-text("Review Me")');
await page.click('a:has-text("Edit")');
await page.selectOption('#edit-moderation-state-0-state', 'published');
await page.click('#edit-submit');
// Artikel ist live
await page.goto('/');
await expect(page.locator('.node--type-article'))
.toContainText('Review Me');
});
2. Commerce Checkout
test('Drupal Commerce: Add to Cart & Checkout', async ({ page }) => {
await page.goto('/shop/products');
// Produkt hinzufügen
await page.click('[data-product-id="123"] .add-to-cart');
await expect(page.locator('.cart-block'))
.toContainText('1 item');
// Checkout
await page.goto('/cart');
await page.click('a:has-text("Checkout")');
// Adresse
await page.fill('#edit-payment-information-billing-address-given-name', 'Max');
await page.fill('#edit-payment-information-billing-address-family-name', 'Mustermann');
await page.fill('#edit-payment-information-billing-address-address-line1', 'Teststr. 123');
await page.fill('#edit-payment-information-billing-address-postal-code', '12345');
await page.fill('#edit-payment-information-billing-address-locality', 'Berlin');
// Testgateway
await page.selectOption('#edit-payment-information-payment-method', 'test_gateway');
await page.fill('#edit-payment-information-test-card-number', '4111111111111111');
// Order abschließen
await page.click('#edit-submit');
await expect(page.locator('.checkout-complete'))
.toContainText('Your order number is');
});
3. Media Library Tests
test('Media Library: Upload & Embed Image', async ({ authenticatedPage: page }) => {
await page.goto('/node/add/article');
// Media Library öffnen
await page.click('button:has-text("Add media")');
// In Modal: Upload
const modal = page.locator('.ui-dialog');
await modal.locator('input[type="file"]').setInputFiles('tests/fixtures/test.jpg');
// Alt-Text eingeben
await modal.fill('#edit-media-0-fields-field-media-image-0-alt', 'Test Image');
// Media speichern
await modal.click('button:has-text("Save")');
// Media auswählen
await modal.click('.media-library-item input[type="checkbox"]');
await modal.click('button:has-text("Insert selected")');
// Im WYSIWYG eingebettet
const editor = page.frameLocator('.cke_wysiwyg_frame');
await expect(editor.locator('img[alt="Test Image"]')).toBeVisible();
// Artikel speichern
await page.fill('#edit-title-0-value', 'Article with Media');
await page.click('#edit-submit');
// Bild wird angezeigt
await expect(page.locator('.field--name-body img'))
.toHaveAttribute('alt', 'Test Image');
});
Erweiterte Features
1. API Testing (JSON:API)
test('JSON:API: Create Node via REST', async ({ request }) => {
// Auth Token holen
const tokenResponse = await request.post('/user/login?_format=json', {
data: {
name: 'admin',
pass: 'admin123'
}
});
const token = (await tokenResponse.json()).csrf_token;
// Node erstellen
const response = await request.post('/jsonapi/node/article', {
headers: {
'Content-Type': 'application/vnd.api+json',
'X-CSRF-Token': token,
},
data: {
data: {
type: 'node--article',
attributes: {
title: 'API Test Article',
body: {
value: 'Created via JSON:API',
format: 'basic_html'
}
}
}
}
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
const nodeUuid = body.data.id;
// Cleanup
await request.delete(`/jsonapi/node/article/${nodeUuid}`, {
headers: { 'X-CSRF-Token': token }
});
});
2. Multi-Language Tests
test('Multilingual Content', async ({ authenticatedPage: page }) => {
// Deutsch
await page.goto('/de/node/add/article');
await page.fill('#edit-title-0-value', 'Deutscher Titel');
await page.click('#edit-submit');
// Übersetzung hinzufügen
await page.click('a:has-text("Translate")');
await page.click('a:has-text("English")');
await page.fill('#edit-title-0-value', 'English Title');
await page.click('#edit-submit');
// Language Switcher testen
await page.goto('/de/deutscher-titel');
await expect(page.locator('h1')).toContainText('Deutscher Titel');
await page.click('a[hreflang="en"]');
await expect(page.locator('h1')).toContainText('English Title');
await expect(page).toHaveURL(/\/en\/english-title/);
});
3. Search API Tests
test('Search API: Index & Search', async ({ page }) => {
// Trigger Indexierung (via Drush oder UI)
await page.goto('/admin/config/search/search-api');
await page.click('button:has-text("Index now")');
await expect(page.locator('.messages--status'))
.toContainText('Successfully indexed');
// Suche durchführen
await page.goto('/');
await page.fill('#edit-search-api-fulltext', 'Drupal');
await page.click('#edit-submit');
// Ergebnisse prüfen
await expect(page.locator('.search-results')).toBeVisible();
await expect(page.locator('.search-result').first())
.toContainText('Drupal');
});
Monitoring & Reporting
1. Custom HTML Reporter
// playwright.config.ts
export default defineConfig({
reporter: [
['html', { outputFolder: 'playwright-report' }],
['json', { outputFile: 'test-results.json' }],
['junit', { outputFile: 'junit.xml' }],
],
});
2. Slack Notifications
// tests/utils/slack-reporter.ts
import { Reporter, TestCase, TestResult } from '@playwright/test/reporter';
class SlackReporter implements Reporter {
onEnd(result: FullResult) {
const message = {
text: `Playwright Tests: ${result.status}`,
attachments: [{
color: result.status === 'passed' ? 'good' : 'danger',
fields: [
{ title: 'Passed', value: result.passed, short: true },
{ title: 'Failed', value: result.failed, short: true },
]
}]
};
fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify(message)
});
}
}
export default SlackReporter;
3. Grafana Dashboard Integration
test.afterEach(async ({ page }, testInfo) => {
const metrics = await page.evaluate(() => ({
duration: performance.now(),
memory: performance.memory?.usedJSHeapSize,
}));
// An Prometheus Pushgateway senden
await fetch('http://pushgateway:9091/metrics/job/playwright', {
method: 'POST',
body: `
test_duration_ms{test="${testInfo.title}"} ${testInfo.duration}
test_status{test="${testInfo.title}"} ${testInfo.status === 'passed' ? 1 : 0}
`
});
});
Troubleshooting
Problem: Tests sind instabil / “flaky”
Lösung 1: Nutze Auto-Waiting richtig
// [Flaky]
await page.click('#submit');
await expect(page.locator('.success')).toBeVisible(); // Kann zu früh sein
// [Robust]
await page.click('#submit');
await page.waitForLoadState('networkidle');
await expect(page.locator('.success')).toBeVisible();
Lösung 2: Erhöhe Timeouts für langsame Drupal-Operationen
test('Slow Drupal Operation', async ({ page }) => {
test.setTimeout(60000); // 60s für diesen Test
await page.goto('/admin/config/development/performance');
await page.click('#edit-clear', { timeout: 30000 }); // 30s für Cache Clear
});
Problem: CKEditor nicht erkannt
Lösung: Frame-Handling
// CKEditor 4 (iFrame)
const editorFrame = page.frameLocator('.cke_wysiwyg_frame');
await editorFrame.locator('body').fill('Text');
// CKEditor 5 (kein iFrame)
await page.locator('.ck-editor__editable').fill('Text');
Problem: AJAX-Requests nicht abgewartet
Lösung: Network Idle oder Response Waiting
// Option 1: Network Idle
await page.goto('/admin/content', { waitUntil: 'networkidle' });
// Option 2: Spezifische Response warten
await Promise.all([
page.waitForResponse(resp => resp.url().includes('/jsonapi')),
page.click('#ajax-trigger')
]);
Problem: Headless vs. Headed unterschiedlich
Lösung: Browser-Argumente anpassen
// playwright.config.ts
use: {
launchOptions: {
args: [
'--disable-blink-features=AutomationControlled',
'--disable-web-security', // Nur für lokale Tests!
]
}
}
Kosten-Nutzen-Analyse
Setup-Zeit
- Initial: 2-4 Stunden (Installation + Konfiguration)
- Erster Test: 30 Minuten
- 10 Tests: ~1 Tag
- CI/CD Integration: 2-3 Stunden
Wartungsaufwand
- Stabil: ~2-5% der ursprünglichen Entwicklungszeit
- Selectors mit data-testid: Kaum Wartung bei Theme-Updates
- Page Object Pattern: Änderungen zentral an einer Stelle
ROI Break-Even
- Kleine Projekte (1-2 Entwickler): Nach ~3 Monaten
- Mittelgroße Projekte (3-5 Entwickler): Nach ~6 Wochen
- Enterprise (5+ Entwickler): Nach ~2 Wochen
Verhinderte Bugs (Durchschnitt)
- Pre-Production: 12-15 Bugs/Monat verhindert
- Production: 3-5 kritische Incidents/Jahr vermieden
- Kosten pro Production Bug: ~2.000-5.000€
- Einsparung: 6.000-25.000€/Jahr
Zusammenfassung
Playwright bietet Drupal-Entwicklern eine moderne, schnelle Alternative zu traditionellen Testing-Tools. Mit Auto-Waiting, Multi-Browser-Support und exzellenten Developer Tools lassen sich robuste End-to-End Tests schreiben, die:
- Schneller laufen als Selenium/WebDriver
- Weniger maintenance benötigen durch Auto-Waiting
- Besser debuggbar sind mit UI Mode & Trace Viewer
- Cross-Browser out-of-the-box funktionieren
- CI/CD-ready sind mit Docker Images
Kombiniert mit Drupal’s Backend-Tests (PHPUnit, FunctionalJavascript) entsteht eine umfassende Testing-Strategie, die Qualität sichert und Entwicklungszeit spart.
Nächste Schritte:
npm init playwright@latestin deinem Drupal-Projekt ausführen- Ersten Login-Test schreiben (5 Minuten)
- Page Object Pattern für häufige Workflows implementieren
- CI/CD Pipeline mit GitHub Actions aufsetzen
- Visual Regression Tests für kritische Seiten hinzufügen
Ressourcen:
Benötigen Sie Unterstützung bei der Playwright-Integration in Ihr Drupal-Projekt? Mit über 20 Jahren Drupal-Erfahrung und Testing-Expertise helfe ich Ihnen gerne bei der Implementierung einer professionellen Test-Strategie. Kontakt: mail@stevenschulz.net oder 04037420859
Häufig gestellte Fragen (FAQ)
Warum Playwright statt PHPUnit für Drupal Tests?
Kann ich Playwright parallel zu Drupal's FunctionalJavascript Tests nutzen?
Wie schnell sind Playwright Tests im Vergleich?
Welche Browser werden unterstützt?
Das könnte Sie auch interessieren
Klaviyo API Integration mit Drupal 11 & Drupal Commerce
Schritt-für-Schritt Anleitung: So integrieren Sie Klaviyo E-Mail-Marketing in Drupal 11. E-Commerce Tracking, Warenkorba...
Drupal Custom Modules: Ein Einsteiger-Guide
Lerne, wie du eigene Drupal-Module entwickelst. Von der .info.yml bis zum ersten Controller und Routing.
Drupal 11 und Gemini 3 Pro: Die Zukunft der KI-Integration
Wie das neue Gemini 3 Pro Modell die Entwicklung und Content-Erstellung in Drupal 11 revolutioniert. Ein tiefer Einblick...