End to End Tests mit Playwright für Vue Apps
AI generated
<v/>
{ }
Vue.js · Playwright · E2E-Testing · CI/CD
End to End Tests mit Playwright für Vue Apps
stabile Nutzerflows statt flakiger Sleep-Aufrufe

Playwright testet Vue-Anwendungen als Ganzes im echten Browser, mit Auto-Waiting statt fester Timeouts, mit Netzwerk-Interception statt echtem Backend und mit paralleler Ausführung über mehrere Browser-Engines hinweg. Das macht End-to-End-Tests planbar statt zur Quelle ständiger CI-Flakiness.

19 Min. Lesezeit Auto-Waiting · Page Objects · API-Mocking Playwright 1.4x · Vue 3 · Vite

1. Warum Playwright für Vue-E2E-Tests

Ein End-to-End-Test mit Playwright startet eine Vue-Anwendung genau so, wie ein echter Nutzer sie erlebt: mit vollständigem Routing, echten API-Aufrufen an eine Testumgebung oder gemockte Backends, echtem Rendering und echten Browser-Events. Anders als isolierte Komponententests prüft ein E2E-Test den gesamten Weg vom Landen auf einer Seite bis zum Abschluss einer Aktion, etwa Login, Warenkorb befüllen und Checkout abschließen. Playwright wurde von Grund auf für genau diesen Anwendungsfall entwickelt und bringt native Unterstützung für Chromium, Firefox und WebKit mit, ohne Treiber-Installation oder Selenium-Grid.

Der entscheidende Architekturunterschied zu älteren Tools liegt in der direkten Kommunikation über das Chrome DevTools Protocol beziehungsweise die entsprechenden Pendants für Firefox und WebKit. Dadurch kennt Playwright den tatsächlichen Zustand der Seite in Echtzeit, statt über Polling zu raten, ob ein Element schon interaktionsbereit ist. Für Vue-Anwendungen mit asynchronem Datenladen, Suspense-Boundaries und verzögert gehydrierten Komponenten ist das ein entscheidender Vorteil gegenüber Tools, die auf feste Wartezeiten angewiesen sind.

In diesem Artikel wird Playwright von der Ersteinrichtung über Page Objects, Selektor-Strategien und Mocking bis zur parallelisierten CI-Ausführung aufgebaut, immer mit Fokus auf typische Vue-3- und Nuxt-Anwendungen.

2. Setup und Projektstruktur

Die Installation von Playwright über npm init playwright@latest erzeugt automatisch eine sinnvolle Grundstruktur mit playwright.config.ts, einem tests-Verzeichnis und Beispieltests. Für Vue-Projekte lohnt sich die Konfiguration eines webServer-Eintrags, der den lokalen Dev-Server oder einen Production-Build automatisch startet, bevor die Tests laufen, und ihn nach Testende wieder beendet. So muss niemand im Team manuell an einen laufenden Server denken.

Ein zweiter wichtiger Konfigurationspunkt ist die baseURL, die alle relativen page.goto()-Aufrufe vereinfacht und Umgebungswechsel zwischen lokal, Staging und CI über eine einzige Umgebungsvariable steuerbar macht. Für Vue-Projekte mit mehreren Store-Views oder Sprachversionen empfiehlt sich zusätzlich ein projects-Array, das denselben Testsatz gegen mehrere Basis-URLs oder Locale-Konfigurationen ausführt.


// playwright.config.ts — project setup with automatic dev server
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests/e2e",
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
  reporter: [["html", { open: "never" }], ["list"]],
  use: {
    baseURL: process.env.BASE_URL || "http://localhost:5173",
    trace: "on-first-retry",
    screenshot: "only-on-failure",
    video: "retain-on-failure",
  },
  webServer: {
    command: "npm run dev",
    url: "http://localhost:5173",
    reuseExistingServer: !process.env.CI,
    timeout: 30_000,
  },
  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
    { name: "webkit", use: { ...devices["Desktop Safari"] } },
  ],
});

Mit dieser Konfiguration laufen alle Tests automatisch gegen einen frisch gestarteten Dev-Server, Traces und Screenshots werden nur bei Fehlschlägen erzeugt, was Speicherplatz spart, und die drei wichtigsten Browser-Engines werden parallel abgedeckt. Für ein Vue-Projekt reicht dieses Setup in der Regel für die gesamte Projektlaufzeit aus, ohne größere Anpassungen.

3. Page Objects für wartbare Testsuiten

Ohne Struktur wachsen E2E-Testsuiten schnell zu unübersichtlichen Sammlungen von Selektoren und Interaktionen. Das Page-Object-Pattern kapselt Selektoren und Aktionen einer Seite oder eines wiederkehrenden UI-Bereichs in einer eigenen Klasse. Ändert sich später ein Selektor in der Vue-Komponente, muss nur das Page Object angepasst werden, nicht jeder einzelne Test, der diese Seite verwendet.

Für Vue-Anwendungen mit wiederverwendbaren Komponenten wie einer globalen Navigation oder einem Warenkorb-Widget lohnt sich zusätzlich ein separates Page Object für diese Komponenten, unabhängig von der konkreten Seite. So lässt sich etwa ein CartWidget-Objekt in Tests für die Produktseite genauso verwenden wie in Tests für die Checkout-Seite, ohne Duplikation der Selektoren.


// tests/e2e/pages/LoginPage.ts — Page Object encapsulating selectors and actions
import { Page, Locator, expect } from "@playwright/test";

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorBanner: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByTestId("email-input");
    this.passwordInput = page.getByTestId("password-input");
    this.submitButton = page.getByTestId("submit-button");
    this.errorBanner = page.getByTestId("error-banner");
  }

  async goto() {
    await this.page.goto("/login");
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  async expectError(message: string) {
    await expect(this.errorBanner).toContainText(message);
  }
}

// tests/e2e/login.spec.ts — using the Page Object in a test
import { test, expect } from "@playwright/test";
import { LoginPage } from "./pages/LoginPage";

test("shows an error for invalid credentials", async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login("user@example.com", "wrong-password");
  await loginPage.expectError("Ungueltige Anmeldedaten");
});

4. Selektor-Strategie: data-testid statt CSS-Klassen

Die Wahl der Selektoren entscheidet über die Stabilität einer gesamten E2E-Suite. CSS-Klassen aus Tailwind ändern sich häufig aus rein stilistischen Gründen, Text-Selektoren brechen bei Übersetzungen, und generierte IDs aus Vue-Komponenten sind oft instabil zwischen Builds. Die robusteste Lösung ist ein dediziertes data-testid-Attribut, das ausschließlich für Tests existiert und von Design-Änderungen komplett unabhängig ist.

Playwright unterstützt diese Strategie nativ über page.getByTestId(), das standardmäßig auf das Attribut data-testid zugreift, aber über selectors.setTestIdAttribute() auch auf andere Attributnamen konfigurierbar ist. In Vue-Komponenten empfiehlt sich, data-testid nur auf Elementen zu setzen, die tatsächlich Testziele sind, nicht auf jedem einzelnen Tag, um das Markup nicht unnötig aufzublähen.


// tests/e2e/checkout.spec.ts — stable selectors via data-testid, resilient to CSS refactors
import { test, expect } from "@playwright/test";

test("completes checkout with a valid payment method", async ({ page }) => {
  await page.goto("/checkout");

  // Stable regardless of Tailwind class changes or copy edits
  await page.getByTestId("payment-method-select").selectOption("credit-card");
  await page.getByTestId("card-number-input").fill("4242424242424242");
  await page.getByTestId("card-expiry-input").fill("12/28");
  await page.getByTestId("card-cvc-input").fill("123");

  await page.getByTestId("place-order-button").click();

  await expect(page.getByTestId("order-confirmation")).toBeVisible();
  await expect(page).toHaveURL(/\/order-confirmation\/\d+/);
});

5. Auto-Waiting statt expliziter Sleep-Aufrufe

Ein zentrales Designprinzip von Playwright ist Auto-Waiting: Bevor eine Aktion wie click() oder fill() ausgeführt wird, wartet Playwright automatisch, bis das Zielelement sichtbar, aktiviert und nicht durch ein anderes Element verdeckt ist. Für Vue-Anwendungen, in denen Komponenten durch v-if, asynchrones Laden oder Transition-Animationen erst verzögert im DOM erscheinen, entfällt damit die Notwendigkeit fester setTimeout-Wartezeiten, die in klassischen Selenium-Suiten üblich waren.

Wo Auto-Waiting an seine Grenzen stößt, etwa bei komplett asynchronen Zustandsübergängen ohne sichtbares DOM-Signal, bietet Playwright explizite Warteprimitiven wie expect.poll() oder page.waitForResponse(). Diese sind gezielten Wartefällen vorbehalten und ersetzen niemals pauschale Sleep-Aufrufe, die Tests künstlich verlangsamen und trotzdem nicht zuverlässiger machen.


// tests/e2e/product-search.spec.ts — relying on auto-waiting, no manual sleeps
import { test, expect } from "@playwright/test";

test("filters products after debounced search input", async ({ page }) => {
  await page.goto("/products");

  await page.getByTestId("search-input").fill("keyboard");

  // No sleep needed — Playwright auto-waits for the DOM to settle
  await expect(page.getByTestId("product-card")).toHaveCount(3);

  // Explicit wait only for a network response with no visible DOM signal
  const responsePromise = page.waitForResponse((res) =>
    res.url().includes("/api/products/search") && res.status() === 200
  );
  await page.getByTestId("sort-select").selectOption("price-asc");
  await responsePromise;

  await expect(page.getByTestId("product-card").first()).toContainText("EUR");
});

6. API-Mocking und Netzwerk-Interception

Playwright bietet mit page.route() ein mächtiges Werkzeug, um Netzwerkanfragen abzufangen, bevor sie eine echte API erreichen. Das ist besonders wertvoll für Vue-Anwendungen, die von externen Zahlungsanbietern, Drittanbieter-APIs oder instabilen Testumgebungen abhängen. Statt gegen ein echtes Backend zu testen, das eigene Latenz, Ausfälle und Rate-Limits mitbringt, antwortet Playwright direkt mit einer kontrollierten Fixture.

Ein wichtiger Anwendungsfall ist das gezielte Erzeugen von Fehlerzuständen, die im echten Backend kaum reproduzierbar wären, etwa ein 503-Fehler mitten in einem Checkout-Flow. Über route.fulfill() lässt sich exakt steuern, welcher Statuscode, welcher Response-Body und welche Header zurückgegeben werden, was gezielte Tests für Fehlerbehandlung, Retry-Logik und Fallback-UI in der Vue-Anwendung ermöglicht.


// tests/e2e/order-history.spec.ts — intercepting API calls with page.route
import { test, expect } from "@playwright/test";

test("shows order history from a mocked API response", async ({ page }) => {
  await page.route("**/api/orders", async (route) => {
    await route.fulfill({
      status: 200,
      contentType: "application/json",
      body: JSON.stringify([
        { id: 101, status: "delivered", total: 89.9 },
        { id: 102, status: "in-transit", total: 34.5 },
      ]),
    });
  });

  await page.goto("/orders");

  await expect(page.getByTestId("order-row")).toHaveCount(2);
  await expect(page.getByTestId("order-row").first()).toContainText("delivered");
});

test("shows a retry prompt when the API responds with an error", async ({ page }) => {
  await page.route("**/api/orders", (route) => route.fulfill({ status: 503 }));

  await page.goto("/orders");

  await expect(page.getByTestId("error-banner")).toBeVisible();
  await expect(page.getByTestId("retry-button")).toBeVisible();
});

7. Visuelle Regressionstests und Screenshots

Neben funktionalen Prüfungen bietet Playwright integrierte visuelle Regressionstests über expect(page).toHaveScreenshot(). Ein Referenzscreenshot wird beim ersten Lauf erzeugt und bei jedem weiteren Testlauf pixelgenau, mit konfigurierbarer Toleranz, verglichen. Für Vue-Komponentenbibliotheken oder Design-Systeme mit vielen visuellen Zuständen ist das ein wertvolles Sicherheitsnetz gegen ungewollte CSS-Regressionen, die funktionale Tests nicht erfassen würden.

Wichtig ist, visuelle Tests bewusst sparsam einzusetzen, nur auf stabilen, deterministischen Seiten ohne zufällige Inhalte, animierte Elemente oder zeitabhängige Daten. Dynamische Inhalte wie Zeitstempel oder zufällig generierte IDs müssen vor dem Screenshot maskiert oder durch feste Testdaten ersetzt werden, sonst produziert der Vergleich ständig falsche Fehlschläge.

8. Parallelisierung und CI-Integration

Playwright führt Tests standardmäßig parallel über mehrere Worker-Prozesse aus, was die Gesamtlaufzeit einer großen E2E-Suite drastisch reduziert. In der CI-Konfiguration lässt sich die Anzahl der Worker gezielt an die verfügbaren CPU-Kerne des Runners anpassen. Für besonders große Vue-Projekte unterstützt Playwright zusätzlich Sharding, bei dem die gesamte Testsuite auf mehrere unabhängige CI-Jobs aufgeteilt wird, die parallel auf unterschiedlichen Maschinen laufen.

Der eingebaute trace-Modus mit der Option on-first-retry erzeugt bei fehlgeschlagenen Tests eine vollständige Aufzeichnung inklusive DOM-Snapshots, Netzwerk-Log und Konsolenausgaben, die sich mit npx playwright show-trace lokal abspielen lässt. Das reduziert die Zeit für die Fehlersuche in der CI erheblich, weil Entwickler den exakten Zustand der Seite zum Fehlerzeitpunkt Schritt für Schritt nachvollziehen können, statt den Fehler lokal erst reproduzieren zu müssen.

9. Playwright im Vergleich zu Cypress

Playwright und Cypress sind beide etablierte Werkzeuge für End-to-End-Tests, unterscheiden sich aber in wichtigen architektonischen Details, die bei der Werkzeugwahl für ein Vue-Projekt eine Rolle spielen sollten.

Kriterium Playwright Cypress
Browser-Engines Chromium, Firefox, WebKit nativ Chromium-basiert, WebKit nur experimentell
Mehrere Tabs / Kontexte Vollständig unterstützt Eingeschränkt, ein Tab pro Test
Auto-Waiting Ja, für alle Aktionen Ja, für alle Aktionen
Parallelisierung out of the box Ja, Worker + Sharding Nur mit Cypress Cloud (kostenpflichtig)
Component Testing Experimentell Ausgereift, first-class Feature

Für reine End-to-End-Suiten mit Fokus auf Cross-Browser-Abdeckung und kostenloser Parallelisierung liegt Playwright vorn. Für Teams, die zusätzlich Component Testing in derselben Toolchain wollen, ist Cypress oft die pragmatischere Wahl. Viele Vue-Projekte setzen beide Werkzeuge parallel ein: Cypress für Component Testing, Playwright für die schlanke E2E-Suite über mehrere Browser-Engines.

Mironsoft

Stabile E2E-Testsuiten und CI-Pipelines für Vue- und Nuxt-Anwendungen

Flakige E2E-Tests satt, aber ohne Zeit für den Umbau?

Wir bauen Playwright-Testsuiten für eure Vue-Anwendung auf, richten Page Objects und API-Mocking sauber ein und integrieren parallele Testläufe in eure bestehende CI-Pipeline.

Test-Architektur

Page Objects, Selektor-Strategie und Ordnerstruktur für wartbare Suiten

API-Mocking

Netzwerk-Interception mit page.route für deterministische Testläufe

CI-Parallelisierung

Sharding und Worker-Konfiguration für schnelle Pipeline-Laufzeiten

10. Zusammenfassung

End-to-End-Tests mit Playwright ersetzen fragile, auf festen Wartezeiten basierende Testsuiten durch ein Werkzeug, das den tatsächlichen Zustand einer Vue-Anwendung in Echtzeit kennt. Auto-Waiting eliminiert die Notwendigkeit expliziter Sleep-Aufrufe, Page Objects halten Selektoren wartbar, und page.route() ermöglicht deterministisches API-Mocking ohne Abhängigkeit von echten Backends. Native Unterstützung für Chromium, Firefox und WebKit deckt Cross-Browser-Kompatibilität ab, ohne zusätzliche Treiber oder Grid-Infrastruktur.

Der größte Gewinn entsteht, wenn Teams konsequent auf data-testid-Selektoren statt CSS-Klassen setzen und visuelle Regressionstests gezielt, nicht flächendeckend, einsetzen. In Kombination mit Parallelisierung über Worker und Sharding lässt sich eine umfangreiche E2E-Suite auch für größere Vue-Projekte in wenigen Minuten statt Stunden ausführen, was E2E-Tests wieder alltagstauglich für jede Pull-Request-Pipeline macht.

End to End Tests mit Playwright — Das Wichtigste auf einen Blick

Auto-Waiting

Playwright wartet automatisch auf Sichtbarkeit und Interaktivität, keine expliziten Sleep-Aufrufe nötig.

Page Objects

Kapseln Selektoren und Aktionen pro Seite, machen Suiten wartbar bei Markup-Änderungen.

API-Mocking

page.route() fängt Netzwerkanfragen ab und liefert kontrollierte Fixtures statt echtem Backend.

Parallelisierung

Worker und Sharding reduzieren die Gesamtlaufzeit großer Suiten in der CI erheblich.

11. FAQ: End to End Tests mit Playwright für Vue Apps

1Warum gut für asynchrones Laden?
Playwright kennt den DOM-Zustand in Echtzeit und wartet automatisch, bis Elemente interaktiv sind, ohne feste Timeouts.
2data-testid statt CSS-Klassen?
Existiert nur für Tests, bleibt unberührt von Design-Refactorings, CSS-Änderungen und Übersetzungen.
3API-Aufrufe mocken?
Mit page.route() Netzwerkanfragen abfangen und mit route.fulfill() eine kontrollierte Antwort liefern.
4Was sind Page Objects?
Kapseln Selektoren und Aktionen einer Seite. Aenderungen am Markup treffen nur das Page Object, nicht jeden Test.
5Noch explizite Wartezeiten nötig?
In der Regel nicht, Auto-Waiting deckt die meisten Faelle ab. Nur fuer Sonderfaelle page.waitForResponse nutzen.
6Visuelle Regressionstests?
expect(page).toHaveScreenshot() vergleicht pixelgenau. Dynamische Inhalte vorher maskieren.
7Große Suite beschleunigen?
Parallele Worker und Sharding ueber mehrere CI-Jobs hinweg.
8Nutzen des Trace-Modus?
Zeichnet DOM, Netzwerk und Konsole auf, abspielbar mit npx playwright show-trace fuer schnelle Fehlersuche.
9Playwright oder Cypress?
Playwright fuer Cross-Browser und Parallelisierung, Cypress fuer ausgereiftes Component Testing. Beide oft kombiniert.
10Mehrere Sprachversionen testen?
Ueber ein projects-Array, das denselben Testsatz gegen mehrere Basis-URLs oder Locales ausfuehrt.