Als wir die Anfrage erhielten, Cypress-Tests zu Playwright zu migrieren, erwarteten wir einen langen, mühsamen Umschreibungsprozess.
Überraschenderweise konnten wir mit Hilfe von GitHub Copilot die Migration von Cypress zu Playwright innerhalb weniger Tage starten – und dabei unsere CI-Pipeline die ganze Zeit am Laufen halten, ohne Testprozesse zu unterbrechen.
In diesem Artikel werde ich erläutern, wie wir diese Migration angegangen sind, welche Lektionen wir dabei gelernt haben und wie eine KI-gestützte Code-Migration den manuellen Aufwand bei Framework-Übergängen drastisch reduzieren kann.
Projektkontext
Das Projekt war eine komplexe Gesundheitsplattform, unter deren Oberfläche viel passierte. Unser Test-Setup umfasste:
- End-to-End- (E2E) und Integrationstests
- Eine benutzerdefinierte TAF-Architektur mit Setups, Teardowns und Entitätserstellung
- Multi-Umgebungs-Konfigurationen
- Dynamische Daten und tiefgreifende Navigationslogik
Insgesamt liefen 148 E2E-Tests auf Cypress – alle funktionierten einwandfrei, aber die Organisation wollte die QA-Tools teamübergreifend standardisieren, was nur eines bedeuten konnte: die Migration zu Playwright.
Warum GitHub Copilot für die Migration verwenden?
Als die Migrationsanfrage kam, sah ich eine Gelegenheit, mit der Testautomatisierung von GitHub Copilot zu experimentieren. Anstatt Hunderte von Testdateien manuell umzuschreiben, wollte ich herausfinden, wie weit wir die KI-Unterstützung treiben konnten, um die Syntaxkonvertierung von Cypress zu Playwright zu automatisieren.
Und ehrlich gesagt, es funktionierte weitaus besser als erwartet.
GitHub Copilot übernahm den Großteil der sich wiederholenden Übersetzung (Selektoren, Befehle, asynchrone Verarbeitung usw.), wodurch 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 Haftungsausschlüsse
KI-generierten Code immer überprüfen
Auch wenn die KI-gestützte Code-Migration die Dinge beschleunigt, müssen Sie jede Zeile überprüfen. Infrastruktur-Migrationen können leicht subtile Fehler oder Performance-Regressionen einführen. Betrachten Sie Copilot als Ihren Pair Programmer, nicht als Autopiloten.
Das alte Framework bis zum Schluss am Laufen halten
Blockieren Sie niemals Releases oder Regressionen während der Migration. Lassen Sie Ihre Cypress-Tests auf CI 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 so um, dass sie in Playwright ausgeführt wird.
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 geholfen hat, beide Frameworks parallel zu betreiben, die CI-Stabilität aufrechtzuerhalten und Tests inkrementell zu migrieren – ohne laufende Releases zu blockieren.
1. Playwright initialisieren und die Ordnerstruktur erstellen
Der erste Schritt war die Initialisierung von Playwright und die Erstellung einer sauberen, isolierten Ordnerstruktur. Wir wollten, dass beide Frameworks nebeneinander im selben Repository existieren, damit wir sie während des Übergangs gleichzeitig ausführen konnten.
Dieses Setup ermöglichte es uns:
- Schrittweise migrieren
- Ergebnisse und Timings zwischen Cypress und Playwright vergleichen
- CI-Pipelines für beide am Laufen 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 ersten Probleme, mit denen wir konfrontiert waren, waren TypeScript-Konflikte – beide Frameworks definieren ähnliche Methodennamen (expect, request usw.), und die gemeinsame Nutzung einer einzigen tsconfig.json führte zu Kompilierungsfehlern.
Die Lösung war einfach: die TypeScript-Konfigurationen aufteilen. 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, gemeinsam genutzte Methoden versehentlich 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 Schwerarbeit zu helfen.
Dazu haben wir einen benutzerdefinierten Prompt erstellt, der definiert, wie sich Copilot beim Umschreiben von Tests von Cypress zu Playwright verhalten soll. Der Prompt teilt dem Agenten mit, was er tun soll, wie er es tun soll und was er ignorieren soll – was konsistente Ergebnisse über alle Migrationen hinweg 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 ihn beispielsweise 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 eher wie ein spezialisierter Migrationsassistent zu verhalten, 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 der Ordner- und Teststruktur beibehalten
- Manuelle Konvertierungszeit um 70–80 % reduzieren
Im Wesentlichen erstellen Sie Ihre eigene KI-Migrations-Toolchain – zugeschnitten auf die Struktur und Konventionen Ihres Projekts.
4. Agent-Modus auswählen und die Migration Schritt für Schritt ausführen
Sobald der Prompt bereit war, bestand der nächste Schritt darin, die Migration tatsächlich mit GitHub Copilot im Agent-Modus auszufü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. Copilot Chat in VS Code öffnen
2. Verwenden Sie /, um die zuvor erstellte Prompt-Datei auszuwählen
3. Verwenden Sie #, um eine bestimmte Datei (zum Beispiel 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 Object
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. Unserer Erfahrung nach trifft der KI-Agent beim ersten Versuch meist 60–70 % des Codes richtig. Der Schlüssel ist, den Konversationskontext aktiv zu halten – verfeinern Sie denselben Chat-Thread kontinuierlich, indem Sie um Korrekturen oder Anpassungen bitten. 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. Zuerst Nicht-Testdateien generieren:
- Page Objects
- Komponenten
- Hilfsdateien
- Konstanten
2. Sobald alle Support-Dateien generiert und verifiziert sind, fügen Sie den Ordner #app zum Chat-Kontext hinzu und migrieren Sie dann die Testdateien selbst.
Verifizierungsschritt
Wenn ein Playwright-Test generiert wird:
1. Überprüfen Sie den Code – bestätigen Sie, dass Seiten-Locatoren, 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 Verifizierung nach jeder Datei halten Sie die Migration stabil und inkrementell, wodurch ein großflächiges Debugging später vermieden wird.
Beispiel für einen migrierten inventoryPage-Test, 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-Test-Suites schrittweise als veraltet kennzeichnen, sobald sie ersetzt wurden
- CI/CD-Pipelines für beide Frameworks betriebsbereit halten
- Eine saubere und modulare Architektur beibehalten
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-Migrations-Prompts. Diese versioniert zu halten, ermöglicht es Ihnen, sie für zukünftige Framework-Migrationen oder Refactorings zu aktualisieren oder wiederzuverwenden..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 als veraltet markiert ist.playwright/-Ordner – dient nun als primäre Automatisierungsquelle für die Zukunft. 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 eine vollständige Typisolierung 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
Nachdem wir fast 150 Tests mit Copilot und Playwright migriert haben, 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 Codezeile, die die KI generiert. Vertrauen Sie ihr niemals blind, besonders bei Infrastruktur-Migrationen.
- Halten Sie sich an konsistente Namenskonventionen.
Verwenden Sie identische Variablen- und Methodennamen über alle Frameworks hinweg – dies hilft dem KI-Agenten, Code genauer zu migrieren und erleichtert Ihnen das spätere Auffinden und Importieren.

- Importe manuell neu erstellen.
Copilot neigt dazu, Importpfade zu vermasseln. Es ist oft schneller, sie zu löschen und manuell neu zu importieren oder globale Importe einzurichten wie: import x from "playwright/foo/bar";
- Denselben Chat für alle Migrationen wiederverwenden.
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 auf einmal. Kleinere Aufgaben führen zu genaueren Ergebnissen und erleichtern die manuelle Validierung.
Playwright-spezifische Ratschläge
- Erstellen Sie niemals unnötigerweise mehrere Instanzen desselben Page Object.
Dies kann zu inkonsistenten Zuständen oder fehlerhaften Selektoren führen, wenn zwischen Seiten navigiert wird. 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,
dotenvzu konfigurieren für Umgebungsvariablen und Anmeldeinformationen. - 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 erkunden möchten – einschließlich Prompt-Dateien, tsconfigs und CI-Setup – können Sie das vollständige Repository hier einsehen: Repo-Beispiel














