Als wir die Anfrage erhielten, Cypress-Tests auf Playwright zu migrieren, erwarteten wir einen langen, mühsamen Umschreibungsprozess.
Überraschenderweise gelang es uns mit Hilfe von GitHub Copilot, die Migration von Cypress zu Playwright in nur wenigen Tagen in die Wege zu leiten – und dabei unsere CI-Pipeline die ganze Zeit am Laufen zu halten, ohne Testprozesse zu unterbrechen.
In diesem Artikel werde ich teilen, wie wir diese Migration angegangen sind, welche Lektionen wir dabei gelernt haben und wie KI-gestützte Code-Migration den manuellen Aufwand bei Framework-Übergängen drastisch reduzieren kann.
Projektkontext
Das Projekt war eine komplexe Gesundheitsplattform mit vielen internen Abläufen. Unser Test-Setup umfasste:
- End-to-End- (E2E) und Integrationstests
- Eine benutzerdefinierte TAF-Architektur mit Setups, Teardowns und Entitätserstellung
- Konfigurationen für mehrere Umgebungen
- Dynamische Daten und komplexe Navigationslogik
Insgesamt hatten wir 148 E2E-Tests, die auf Cypress liefen – alle funktionierten einwandfrei, aber die Organisation wollte die QA-Tools teamübergreifend standardisieren, was nur eines bedeutete: Playwright-Migration.
Warum GitHub Copilot für die Migration verwenden?
Als die Migrationsanfrage kam, sah ich eine Gelegenheit, mit GitHub Copilot im Bereich Testautomatisierung zu experimentieren. Anstatt hunderte von Testdateien manuell umzuschreiben, wollte ich sehen, wie weit wir die KI-Unterstützung treiben könnten, um die Syntaxkonvertierung von Cypress zu Playwright zu automatisieren.
Und ehrlich gesagt, es hat weit besser funktioniert als erwartet.
GitHub Copilot übernahm den Großteil der sich wiederholenden Übersetzung (Selektoren, Befehle, asynchrone Verarbeitung usw.), sodass wir uns auf Infrastrukturanpassungen und Feinabstimmungen konzentrieren konnten.
Innerhalb von 1–2 Tagen hatten wir bereits einen funktionierenden Prototyp des Playwright-Testautomatisierungs-Frameworks.
Zwei wichtige Hinweise
Generierten KI-Code immer überprüfen
Obwohl die KI-gestützte Code-Migration die Dinge beschleunigt, müssen Sie jede Zeile überprüfen. Infrastruktur-Migrationen können leicht subtile Fehler oder Leistungseinbußen verursachen. Betrachten Sie Copilot als Ihren Pair Programmer, nicht als Autopiloten.
Das alte Framework bis zum Ende am Laufen halten
Blockieren Sie niemals Releases oder Regressionen während der Migration. Halten Sie Ihre Cypress-Tests auf CI am Laufen, bis die entsprechenden Suiten in Playwright vollständig stabil sind. Sobald eine Suite migriert und validiert wurde, entfernen Sie sie aus Cypress und stellen Sie CI auf Playwright um.
Diese schrittweise Einführung gewährleistet null Ausfallzeiten und eine kontinuierliche Testabdeckung während des gesamten Prozesses.
Workflow zur Migration von Cypress zu Playwright
Nachdem die Grundlagen geklärt waren, begannen wir mit der praktischen Migration.
Unten ist der Workflow beschrieben, der uns half, beide Frameworks parallel zu betreiben, die CI-Stabilität aufrechtzuerhalten und Tests schrittweise zu migrieren – ohne laufende Releases zu blockieren.
1. Playwright initialisieren und Ordnerstruktur erstellen
Der erste Schritt war die Initialisierung von Playwright und die Erstellung einer sauberen, isolierten Ordnerstruktur. Wir wollten, dass beide Frameworks Seite an Seite im selben Repository existieren, damit wir sie während des Übergangs gleichzeitig ausführen konnten.
Dieses Setup ermöglichte uns:
- Schrittweise zu migrieren
- Ergebnisse und Zeitabläufe zwischen Cypress und Playwright zu vergleichen
- CI-Pipelines für beide am Laufen zu halten
Initialisierungsbeispiel:

Hier ist die vorgeschlagene Ordnerstruktur, die wir für Playwright verwendet haben:
├── cypress/
│ ├── app/
│ ├── downloads/
│ ├── e2e/
│ ├── fixtures/
│ ├── screenshots/
│ ├── support/
│ └── videos/
├── playwright/
│ ├── app/
│ │ ├── components/
│ │ ├── fixtures/
│ │ └── pageobjects/
│ ├── constants/
│ ├── helpers/
│ ├── reports/
│ ├── test-results/
│ └── tests/
├── .gitignore
├── package.json
├── playwright.config.ts
├── smoke.config.ts
├── tsconfig.json2. Separate tsconfig-Dateien für jedes Framework konfigurieren
Eines der frühen Probleme, auf das wir stießen, waren TypeScript-Konflikte – beide Frameworks definieren ähnliche Methodennamen (expect, request usw.), und die gemeinsame Nutzung einer einzelnen tsconfig.json führte zu Kompilierungsfehlern.
Die Lösung war einfach: die TypeScript-Konfigurationen aufzuteilen. Diese Struktur stellt sicher, dass jedes Framework seine eigenen Typdefinitionen initialisiert und Typkollisionen vermieden werden:
├── tsconfig.json
├── tsconfig.cypress.json
├── tsconfig.playwright.jsonHaupt-tsconfig.json
Fügen Sie die Option composite hinzu, um Projektverweise zu unterstützen.
tsconfig.json
{
"compilerOptions": {
"composite": true
}
}tsconfig.cypress.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["cypress", "@testing-library/cypress", "node"],
"isolatedModules": false
},
"include": ["cypress/**/*.ts"]
}tsconfig.playwright.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["node", "@playwright/test"]
},
"include": ["playwright/**/*.ts", "playwright.config.ts"]
}Mit dieser Trennung konnten beide Frameworks friedlich in einem Repository koexistieren – keine Compiler-Konflikte mehr und kein Risiko, versehentlich gemeinsam genutzte Methoden zweimal zu initialisieren.
3. Einen Prompt erstellen, um die Migration mit GitHub Copilot zu automatisieren
Nachdem Playwright initialisiert und das Projekt strukturiert war, war es an der Zeit, GitHub Copilot bei der Schwerstarbeit zu unterstützen.
Dazu haben wir einen benutzerdefinierten Prompt erstellt, der definiert, wie sich Copilot beim Umschreiben von Tests von Cypress zu Playwright verhalten soll. Der Prompt weist den Agenten an, was zu tun ist, wie es zu tun ist und was ignoriert werden soll – was konsistente Ergebnisse bei allen Migrationen liefert.
So fügen Sie den Prompt hinzu
Öffnen Sie in Ihrem GitHub-Repository das Zahnradsymbol ⚙️ im Copilot Chat und erstellen Sie einen neuen Prompt.
Die Datei wird automatisch unter: ./github/prompts/ gespeichert.
Sie können sie zum Beispiel migrate_tests.md nennen.
Prompt-Beispiel:
---
mode: agent
---
You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate application behavior.
Your task is to help migrate existing Cypress tests to Playwright tests.
The Cypress tests, page object, components, and helper files are provided as input, and you need to generate equivalent Playwright test code.
Output ALL migrated code to the chat.
When generating the Playwright test code or any page objects, ensure that:
1. The test structure follows the same structure as the Cypress tests.
2. Create a global fixture for setup and teardown and reuse it in the tests.
3. All user interactions (clicks, typing, navigation) are accurately translated to Playwright syntax in page objects and test files.
4. Assertions in Cypress are converted to equivalent Playwright assertions.
5. Ignore the network spying and stubbing parts of the Cypress tests.
6. Comment out all the methods with waits (e.g., cy.wait) in the Playwright code.
7. Do not use get methods in page objects; use direct methods instead `backbackItem = () => this.page.locator('[id="item_4_title_link"]');`Warum das hilft
Diese Art von strukturiertem Prompt ermöglicht es GitHub Copilot, sich mehr wie ein spezialisierter Migrationsassistent zu verhalten und nicht wie ein Allzweck-Code-Generator.
Mit dem richtigen Kontext und den richtigen Regeln kann Copilot automatisch:
- Große Teile von Cypress-Tests in Playwright-Syntax umschreiben
- Konsistenz bei Ordner- und Teststrukturen wahren
- Die manuelle Konvertierungszeit um 70–80 % reduzieren
Im Wesentlichen erstellen Sie Ihre eigene KI-Migrations-Toolchain – zugeschnitten auf die Struktur und Konventionen Ihres Projekts.
4. Agentenmodus auswählen und die Migration Schritt für Schritt durchführen
Nachdem der Prompt bereit war, bestand der nächste Schritt darin, die Migration mit GitHub Copilot im Agentenmodus tatsächlich durchzuführen. In dieser Phase ließen wir das KI-Modell jede Datei – Page Objects, Komponenten, Hilfsdateien und schließlich die Tests – einzeln verarbeiten.
Für diese Aufgabe verwendeten wir das GPT-4.1-Modell, das die konsistentesten und kontextsensitivsten Code-Transformationen liefert.
So starten Sie die Migration
1. Öffnen Sie den Copilot Chat in VS Code
2. Verwenden Sie /, um die zuvor erstellte Prompt-Datei auszuwählen
3. Verwenden Sie #, um eine bestimmte Datei (z. B. einen Cypress-Test oder ein Page Object) auszuwählen, die Sie migrieren möchten
Copilot wendet nun Ihren Migrations-Prompt an und generiert eine Playwright-kompatible Version der Datei direkt im Chat.
Beispiel für die Migration des inventoryPage Page Objects
import { ItemComponent } from "../components/item.component";
import Common from "./common";
class InventoryPage extends Common {
item = new ItemComponent();
backbackItem = () => cy.get("#item_4_title_link");
backbackAddItemButton = () => cy.getByTestId("add-to-cart-sauce-labs-backpack");
bikeLightsAddItemButton = () => cy.getByTestId("add-to-cart-sauce-labs-bike-light");
backbackRemoveItemButton = () => cy.getByTestId("remove-sauce-labs-backpack");
shoppingCartLogo = () => cy.get("#shopping_cart_container");
clickBackbackAddItemButton = () => this.backbackAddItemButton().click();
clickBikeLightsAddItemButton = () => this.bikeLightsAddItemButton().click();
clickBackbackRemoveItemButton = () => this.backbackRemoveItemButton().click();
assertCartLogoItems = (itemsNum: number) => this.shoppingCartLogo().should("have.text", itemsNum);
}
export default new InventoryPage();
import { Page, Locator, expect } from "@playwright/test";
import Common from "./common";
class InventoryPage extends Common {
constructor(page: Page) {
super(page);
this.item = new ItemComponent(page);
}
// Locators
backbackItem(): Locator {
return this.page.locator("#item_4_title_link");
}
backbackAddItemButton(): Locator {
return this.page.getByTestId("add-to-cart-sauce-labs-backpack");
}
bikeLightsAddItemButton(): Locator {
return this.page.getByTestId("add-to-cart-sauce-labs-bike-light");
}
backbackRemoveItemButton(): Locator {
return this.page.getByTestId("remove-sauce-labs-backpack");
}
shoppingCartLogo(): Locator {
return this.page.locator("#shopping_cart_container");
}
// Actions
async clickBackbackAddItemButton(): Promise<void> {
await this.backbackAddItemButton().click();
}
async clickBikeLightsAddItemButton(): Promise<void> {
await this.bikeLightsAddItemButton().click();
}
async clickBackbackRemoveItemButton(): Promise<void> {
await this.backbackRemoveItemButton().click();
}
async assertCartLogoItems(itemsNum: number): Promise<void> {
await expect(this.shoppingCartLogo()).toHaveText(String(itemsNum));
}
}
export default InventoryPage;Das Verhalten des Agenten optimieren
Machen Sie sich keine Sorgen, wenn die erste Ausgabe nicht perfekt ist. Aus unserer Erfahrung heraus trifft der KI-Agent in der Regel 60–70 % des Codes beim ersten Versuch richtig. Der Schlüssel ist, den Konversationskontext aktiv zu halten – verfeinern Sie denselben Chat-Thread kontinuierlich, indem Sie nach Korrekturen oder Anpassungen fragen. Dabei lernt Copilot Ihre Projektmuster und produziert genaueren, wiederverwendbaren Code.
Im Wesentlichen gilt: Je mehr Iterationen Sie im selben Chat durchführen, desto besser wird die Migrationsqualität.
Migrationsreihenfolge: Zuerst Dateien, dann Tests
Um Abhängigkeiten konsistent zu halten und fehlende Referenzen zu vermeiden, befolgen Sie diese Reihenfolge:
1. Generieren Sie zuerst Nicht-Testdateien:
- Page Objects
- Komponenten
- Hilfsdateien
- Konstanten
2. Sobald alle Support-Dateien generiert und überprüft wurden, fügen Sie den Ordner #app zum Chat-Kontext hinzu und migrieren Sie dann die Testdateien selbst.
Überprüfungsschritt
Wenn ein Playwright-Test generiert wird:
1. Überprüfen Sie den Code – bestätigen Sie, dass Seiten-Locators, Testschritte und Assertions korrekt sind.
2. Führen Sie den Test mit Ihrer Playwright CLI aus, um sicherzustellen, dass er ordnungsgemäß ausgeführt wird.
3. Beheben Sie kleinere Syntax- oder Fixture-Fehler, falls Copilot einen Cypress-Befehl missverstanden hat.
Durch die Überprüfung nach jeder Datei halten Sie die Migration stabil und inkrementell, wodurch umfangreiches Debugging später vermieden wird.
Beispiel eines migrierten inventoryPage-Tests, der erfolgreich bestanden wird:
import inventoryPage from "cypress/app/pageobjects/inventoryPage";
import loginPage from "cypress/app/pageobjects/loginPage";
import { itemsNames } from "../../fixtures/data.json";
describe("InventoryPage tests", () => {
beforeEach(() => {
loginPage.loginWithValidData();
});
it("The user should add item to the card", () => {
inventoryPage.item.itemByName("Bike Light").should("have.text", itemsNames.bikeLight);
inventoryPage.item.addToCartByName("Bike Light");
inventoryPage.assertCartLogoItems(1);
});
it("The user should remove item from the card", () => {
inventoryPage.backbackItem().should("have.text", itemsNames.backpackItemName);
inventoryPage.clickBackbackAddItemButton();
inventoryPage.assertCartLogoItems(1);
inventoryPage.clickBackbackRemoveItemButton();
inventoryPage.shoppingCartLogo().should("not.have.text");
});
it("The user should add multiple items to the card", () => {
inventoryPage.backbackItem().should("have.text", itemsNames.backpackItemName);
inventoryPage.clickBackbackAddItemButton();
inventoryPage.assertCartLogoItems(1);
inventoryPage.clickBikeLightsAddItemButton();
inventoryPage.assertCartLogoItems(2);
});
});
import { test, expect } from "../fixtures/test.fixture";
import { itemsNames } from "../../constants/data.json";
// InventoryPage tests migrated from Cypress to Playwright
test.describe("InventoryPage tests", () => {
test.beforeEach(async ({ pageManager }) => {
await pageManager.loginPage.loginWithValidData();
});
test("The user should add item to the cart", async ({ pageManager }) => {
await expect(
pageManager.inventoryPage.item.itemByName(itemsNames.bikeLight)).toHaveText(itemsNames.bikeLight);
await pageManager.inventoryPage.item.addToCartByName(itemsNames.bikeLight);
await pageManager.inventoryPage.assertCartLogoItems(1);
});
test("The user should remove item from the cart", async ({ pageManager }) => {
await expect(
pageManager.inventoryPage.backbackItem()).toHaveText(itemsNames.backpackItemName);
await pageManager.inventoryPage.clickBackbackAddItemButton();
await pageManager.inventoryPage.assertCartLogoItems(1);
await pageManager.inventoryPage.clickBackbackRemoveItemButton();
await expect(pageManager.inventoryPage.shoppingCartLogo()).not.toHaveText(/\d/); // Should not have any number text
});
test("The user should add multiple items to the cart", async ({ pageManager }) => {
await expect(pageManager.inventoryPage.backbackItem()).toHaveText(itemsNames.backpackItemName);
await pageManager.inventoryPage.clickBackbackAddItemButton();
await pageManager.inventoryPage.assertCartLogoItems(1);
await pageManager.inventoryPage.clickBikeLightsAddItemButton();
await pageManager.inventoryPage.assertCartLogoItems(2);
});
});5. Endgültige Repository-Struktur nach der Migration
Nach Abschluss der Migration erreichte unser Repository einen stabilen Zustand mit zwei Frameworks – Cypress und Playwright liefen vollständig nebeneinander. Diese Struktur ermöglichte es uns:
- Cypress-Testsuiten schrittweise zu verwerfen, sobald sie ersetzt wurden
- CI/CD-Pipelines für beide Frameworks betriebsbereit zu halten
- Eine saubere und modulare Architektur zu pflegen
Unten ist die endgültige Struktur nach Abschluss des Migrations-Workflows.
Endgültige Ordnerstruktur
├── .env
├── .gitignore
├── readme.md
├── package.json
├── playwright.config.ts
├── smoke.config.ts
├── tsconfig.cypress.json
├── tsconfig.playwright.json
├── tsconfig.json
├── .github/
│ ├── prompts/
│ └── workflows/
├── cypress/
│ ├── app/
│ ├── downloads/
│ ├── e2e/
│ ├── fixtures/
│ ├── screenshots/
│ ├── support/
│ └── videos/
├── playwright/
│ ├── app/
│ ├── constants/
│ ├── helpers/
│ ├── reports/
│ ├── test-results/
│ └── tests/
Hinweise zur Struktur
.github/prompts/– enthält Ihre Copilot-Migrationsprompts. Durch die Versionierung können Sie diese für zukünftige Framework-Migrationen oder Refactorings aktualisieren oder wiederverwenden..github/workflows/– enthält Ihre CI/CD-Definitionen. Sie können sowohl Playwright- als auch Cypress-Jobs parallel ausführen, bis das alte Framework vollständig abgeschafft ist.- Der Ordner
playwright/– fungiert nun als primäre Automatisierungsquelle. Er spiegelt die Struktur der vorherigen Cypress-Implementierung wider, um die Einarbeitung zu erleichtern und Konsistenz zu gewährleisten. tsconfig.playwright.jsonundtsconfig.cypress.json– bleiben für die vollständige Typ-Isolation getrennt, um Konflikte während des Builds zu vermeiden.
Diese Struktur unterstützte nicht nur einen reibungslosen Übergang, sondern positionierte das Projekt auch für zukünftige Skalierbarkeit, CI-Flexibilität und modulare Testverantwortung über Teams hinweg.
Praktische Ratschläge & Gelernte Lektionen
Nach der Migration von fast 150 Tests mit Copilot und Playwright sind hier einige wichtige Lektionen und praktische Tipps, die den Prozess reibungsloser gestalteten (und uns vor einigen Kopfschmerzen bewahrten).
Allgemeine KI-bezogene Ratschläge
- KI lügt – oft überzeugend.
Überprüfen Sie immer jede Zeile Code, die die KI generiert. Vertrauen Sie ihr niemals blind, insbesondere bei Infrastruktur-Migrationen.
- Halten Sie sich an konsistente Namenskonventionen.
Verwenden Sie identische Variablen- und Methodennamen über die Frameworks hinweg – das hilft dem KI-Agenten, den Code genauer zu migrieren, und erleichtert Ihnen das spätere Finden und Importieren.

- Importe manuell neu aufbauen.
Copilot neigt dazu, Importpfade durcheinanderzubringen. Es ist oft schneller, sie zu löschen und manuell neu zu importieren oder globale Importe einzurichten wie: import x from "playwright/foo/bar";
- Verwenden Sie denselben Chat für alle Migrationen.
Bleiben Sie bei einem einzigen Copilot-Chat-Thread – er baut Kontext auf und „erinnert“ sich an Ihre Projektkonventionen, was zu besseren Ergebnissen führt.
- Migrationsaufgaben zerlegen.
Generieren Sie 1–2 Dateien gleichzeitig. Kleinere Aufgaben führen zu genaueren Ergebnissen und erleichtern die manuelle Validierung.
Playwright-spezifische Ratschläge
- Erstellen Sie niemals mehrere Instanzen desselben Page Objects, wenn kein Bedarf besteht.
Dies kann zu inkonsistenten Zuständen oder fehlerhaften Selektoren führen, wenn Sie zwischen Seiten navigieren. Die Lösung? Implementieren Sie einen Page Manager, der alle Page Objects über eine einzige Instanz zugänglich hält.
Hier ist ein minimales Beispiel-Setup:
// login.page.ts
class LoginPage extends Common {
async clickLoginButton(): Promise<void> {
await this.loginButton().click();
}
}
export default LoginPage;
//-----------------------------------------------------------
//PageManager.ts
export class PageManager {
readonly loginPage: LoginPage;
constructor(page: Page) {
this.loginPage = new LoginPage(page);
}
}
//-----------------------------------------------------------
//test.fixture.ts
export const test = base.extend<{
pageManager: PageManager;
}>({
pageManager: async ({ page }, use) => {
const manager = new PageManager(page);
await use(manager);
},
});
export { expect };
//-----------------------------------------------------------
//userLogin.spec.ts
test("The user should login with valid data", async ({ pageManager }) => {
await pageManager.loginPage.clickLoginButton();
});Weitere technische Tipps
- Entfernen Sie alle „get“-Präfixe von Locators, die der KI-Agent generiert – verwenden Sie stattdessen direkte Locator-Methoden.
- Vergessen Sie nicht,
dotenvfür Umgebungsvariablen und Anmeldeinformationen zu konfigurieren. - Passen Sie Ihre Playwright-Konfiguration an, um alle Berichte im Playwright-Ordner auszugeben, damit Ihr Projekt-Root sauber bleibt:
reporter: [["html", { open: "never", outputFolder: "./playwright/reports/" }]],
outputDir: "./playwright/test-results",
Nachwort
Ich habe hier kein ESLint-Setup aufgenommen, da es von Projekt zu Projekt variiert.
Die einzige universelle Empfehlung, die ich geben kann, ist die Installation des Playwright ESLint-Plugins für eine bessere Regeldurchsetzung: eslint-plugin-playwright.
Wenn Sie ein vollständiges funktionierendes Beispiel dieser Migration – einschließlich Prompt-Dateien, tsconfigs und CI-Setup – erkunden möchten, finden Sie das gesamte Repository hier: Repo-Beispiel















