JetBase Logotyp
  • Hem
  • Blogg
  • Migrering av Cypress-tester till Playwright med Copilot
Banner

När vi fick en förfrågan om att migrera Cypress-tester till Playwright, förväntade vi oss en lång och tråkig omskrivningsprocess.

Förvånansvärt nog, med hjälp av GitHub Copilot, lyckades vi påbörja migreringen från Cypress till Playwright på bara ett par dagar — och behålla vår CI-pipeline igång hela tiden utan att avbryta några testprocesser.

I den här artikeln kommer jag att dela med mig av hur vi närmade oss denna migrering, vilka lärdomar vi drog på vägen och hur AI-assisterad kodmigrering drastiskt kan minska den manuella arbetsbördan vid ramverksövergångar.

1

Projektkontext

Projektet var en komplex vårdplattform med mycket som hände under ytan. Vår testuppsättning inkluderade:

  • End-to-end (E2E) och integrationstester
  • En anpassad TAF-arkitektur med uppsättningar, nedmonteringar och entitetsskapande
  • Fler-miljökonfigurationer
  • Dynamisk data och djup navigeringslogik

Totalt hade vi 148 E2E-tester som kördes med Cypress — alla fungerade bra, men organisationen ville standardisera QA-verktyg över teamen, vilket innebar en sak: Playwright-migrering.

2

Varför använda GitHub Copilot för migrering?

När migreringsförfrågan kom in, såg jag en möjlighet att experimentera med GitHub Copilots testautomatisering. Istället för att manuellt skriva om hundratals testfiler, ville jag se hur långt vi kunde driva AI-assistans för att automatisera syntaxkonvertering från Cypress till Playwright.

Och ärligt talat, det fungerade mycket bättre än förväntat.

GitHub Copilot hanterade det mesta av den repetitiva översättningen (selektorer, kommandon, asynkron hantering, etc.), vilket lät oss fokusera på infrastrukturjusteringar och finjusteringar.

Inom 1–2 dagar hade vi redan en fungerande prototyp av Playwright-ramverket för testautomatisering.

3

Två viktiga friskrivningar

Verifiera alltid AI-genererad kod

Även om AI-assisterad kodmigrering snabbar upp processen måste du granska varje rad. Infrastrukturmigreringar kan lätt införa subtila buggar eller prestandaregressioner. Tänk på Copilot som din parprogrammerare, inte en autopilot.

Behåll det gamla ramverket igång tills slutet

Blockera aldrig releaser eller regressioner under migreringen. Behåll dina Cypress-tester körandes på CI tills motsvarande sviter är helt stabila i Playwright. När en svit har migrerats och validerats, ta bort den från Cypress och byt CI för att köra den i Playwright.

Denna gradvisa utrullning säkerställer noll driftstopp och kontinuerlig testtäckning under hela processen.

4

Arbetsflöde för att migrera från Cypress till Playwright

När grundarbetet var klart gick vi över till den praktiska migreringen. 
Nedan följer arbetsflödet som hjälpte oss att köra båda ramverken parallellt, bibehålla CI-stabilitet och påbörja inkrementell migrering av tester — utan att blockera pågående releaser.

1. Initiera Playwright och skapa mappstrukturen

Det första steget var att initiera Playwright och skapa en ren, isolerad mappstruktur. Vi ville att båda ramverken skulle existera sida vid sida i samma repository, så att vi kunde köra dem samtidigt under övergången.

Denna uppsättning gjorde att vi kunde:

  • Migrera gradvis
  • Jämföra resultat och tider mellan Cypress och Playwright
  • Behålla CI-pipelines igång för båda

Initialiseringsexempel:

Initialiseringsexempel.webp

Här är den föreslagna mappstrukturen vi använde för Playwright:

├── 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.json

2. Konfigurera separata tsconfig-filer för varje ramverk

Ett av de tidiga problemen vi stötte på var TypeScript-konflikter — båda ramverken definierar liknande metodnamn (expect, request, etc.), och att dela en enda tsconfig.json ledde till kompileringsfel.

Lösningen var enkel: dela upp TypeScript-konfigurationerna. Denna struktur säkerställer att varje ramverk initialiserar sina egna typdefinitioner och undviker typkollisioner:

├── tsconfig.json
├── tsconfig.cypress.json
├── tsconfig.playwright.json

Huvudsaklig tsconfig.json
Lägg till alternativet composite för att stödja projektreferenser.

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"]
}

Med denna separation på plats kunde båda ramverken samexistera fredligt i ett och samma repository — inga fler kompileringskonflikter, och ingen risk att av misstag initialisera delade metoder två gånger.

3. Skapa en prompt för att automatisera migrering med GitHub Copilot

När Playwright var initialiserat och projektet strukturerat, var det dags att låta GitHub Copilot hjälpa till med det tunga arbetet.

För att göra detta skapade vi en anpassad prompt som definierar hur Copilot ska bete sig när tester skrivs om från Cypress till Playwright. Prompten talar om för agenten vad den ska göra, hur den ska göra det och vad den ska ignorera — vilket ger konsekventa resultat över alla migreringar.

Hur man lägger till prompten

Inuti ditt GitHub-repository, öppna kugghjulsikonen ⚙️ i Copilot Chat och skapa en ny prompt. 
Filen sparas automatiskt under: ./github/prompts/

Du kan namnge den något i stil med migrate_tests.md.

Exempel på prompt:

---
mode: agent
---
Du är en Playwright Test Generator, en expert på webbläsarautomatisering och end-to-end-testning.
Din specialitet är att skapa robusta, pålitliga Playwright-tester som exakt simulerar användarinteraktioner och validerar applikationsbeteende.
Din uppgift är att hjälpa till att migrera befintliga Cypress-tester till Playwright-tester.
Cypress-testerna, page object, komponenter och hjälpfiler tillhandahålls som input, och du måste generera motsvarande Playwright-testkod.
Mata ut ALL migrerad kod till chatten.
När du genererar Playwright-testkoden eller några page objects, se till att:
1. Teststrukturen följer samma struktur som Cypress-testerna.
2. Skapa en global fixture för setup och teardown och återanvänd den i testerna.
3. Alla användarinteraktioner (klick, inmatning, navigering) översätts korrekt till Playwright-syntax i page objects och testfiler.
4. Assertions i Cypress konverteras till motsvarande Playwright-assertions.
5. Ignorera delarna för nätverksspionering och stubbing i Cypress-testerna.
6. Kommentera ut alla metoder med waits (t.ex. cy.wait) i Playwright-koden.
7. Använd inte get-metoder i page objects; använd istället direkta metoder `backbackItem = () => this.page.locator('[id="item_4_title_link"]');`

Varför detta hjälper

Denna typ av strukturerad prompt gör att GitHub Copilot kan bete sig mer som en specialiserad migreringsassistent, inte en allmän kodgenerator.

Med rätt kontext och regler kan Copilot automatiskt:

  • Skriva om stora delar av Cypress-tester till Playwright-syntax
  • Bibehålla konsekvens i mapp- och teststrukturen 
  • Minska manuell konverteringstid med 70–80 %

I huvudsak skapar du din egen AI-migreringsverktygskedja — anpassad till ditt projekts struktur och konventioner.

4. Välj agentläge och kör migreringen steg för steg

När prompten var redo var nästa steg att faktiskt köra migreringen med GitHub Copilot i agentläge. I denna fas lät vi AI-modellen bearbeta varje fil — page objects, komponenter, hjälpfiler och slutligen testerna — en efter en.

För denna uppgift använde vi GPT-4.1-modellen, som levererar de mest konsekventa och kontextmedvetna kodtransformeringarna.

Hur man startar migreringen

1. Öppna Copilot Chat i VS Code

2. Använd / för att välja promptfilen du skapade tidigare

3. Använd # för att välja en specifik fil (till exempel ett Cypress-test eller page object) som du vill migrera

Copilot kommer nu att tillämpa din migreringsprompt och generera en Playwright-kompatibel version av filen direkt i chatten.

Hur man startar migreringen.webpExempel på migrering av 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;

Finjustera agentens beteende

Oroa dig inte om den första outputen inte är perfekt. Av vår erfarenhet får AI-agenten vanligtvis 60–70 % av koden rätt vid första försöket. Nyckeln är att hålla konversationskontexten aktiv — fortsätt att förfina samma chatt-tråd genom att be om korrigeringar eller justeringar. När du gör detta börjar Copilot lära sig dina projektmönster och producerar mer exakt, återanvändbar kod.

I huvudsak, ju fler iterationer du gör i samma chatt, desto bättre blir migreringskvaliteten.

Migreringsordning: Filer först, sedan tester

För att bibehålla konsekventa beroenden och undvika saknade referenser, följ denna ordning:

1. Generera icke-testfiler först:

  • Page Objects
  • Komponenter
  • Hjälpfiler
  • Konstanter

2. När alla stödfiler har genererats och verifierats, lägg till mappen #app till chattkontexten, migrera sedan själva testfilerna.

Verifieringssteg

När ett Playwright-test genereras:

1. Granska koden — bekräfta att sidlokatorer, teststeg och assertions är korrekta.

2. Kör testet med din Playwright CLI för att säkerställa att det exekveras korrekt.

3. Åtgärda mindre syntax- eller fixture-fel om Copilot missförstod ett Cypress-kommando.

Genom att verifiera efter varje fil håller du migreringen stabil och inkrementell, vilket undviker storskalig felsökning senare.

Exempel på migrerat inventoryPage-test som körs med godkänt resultat:

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. Slutlig repository-struktur efter migrering

När migreringen var klar nådde vårt repository ett stabilt, dubbelramverkstillstånd — som fullt ut körde både Cypress och Playwright sida vid sida. Denna struktur gjorde att vi kunde:

  • Gradvis avveckla Cypress-testsviter när de ersattes
  • Behålla CI/CD-pipelines operativa för båda ramverken
  • Bibehålla en ren och modulär arkitektur

Nedan är den slutliga strukturen efter att migreringsarbetsflödet slutförts.

Slutlig mappstruktur

├── .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/

Anmärkningar om strukturen

  • .github/prompts/ — innehåller dina Copilot-migreringsprompter. Att versionera dessa gör att du kan uppdatera eller återanvända dem för framtida ramverksmigreringar eller refaktoreringar.
  • .github/workflows/ — innehåller dina CI/CD-definitioner. Du kan köra både Playwright- och Cypress-jobb parallellt tills det gamla ramverket är helt avvecklat.
  • Mappen playwright/ — fungerar nu som den primära automatiseringskällan framöver. Den speglar strukturen från den tidigare Cypress-implementeringen för att underlätta onboarding och säkerställa konsekvens.
  • tsconfig.playwright.json och tsconfig.cypress.json — förblir separata för fullständig typisolering, vilket förhindrar konflikter under byggprocesser.

Denna struktur stödde inte bara en smidig övergång utan positionerade också projektet för framtida skalbarhet, CI-flexibilitet och modulärt testägarskap över teamen.

5

Praktiska råd & lärdomar

Efter att ha migrerat nästan 150 tester med Copilot och Playwright, här är några viktiga lärdomar och praktiska tips som gjorde processen smidigare (och räddade oss från en del huvudvärk).

Allmänna AI-relaterade råd

  • AI ljuger — ofta övertygande.  

Verifiera alltid varje kodrad som AI:n genererar. Lita aldrig blint på den, särskilt inte under infrastrukturmigreringar.

  • Håll dig till konsekventa namngivningskonventioner.

Behåll identiska variabel- och metodnamn över ramverk — det hjälper AI-agenten att migrera kod mer exakt och gör det lättare för dig att hitta och importera dem senare.

Allmänna AI-relaterade råd.webp

  • Bygg om importer manuellt.

Copilot tenderar att trassla till importsökvägar. Det är ofta snabbare att ta bort dem och importera om manuellt, eller att ställa in globala importer som: import x from "playwright/foo/bar";

  • Återanvänd samma chatt för alla migreringar.

Håll dig till en enda Copilot-chatt-tråd — den bygger kontext och "kommer ihåg" dina projektkonventioner, vilket leder till bättre resultat.

  • Dekomponera migreringsuppgifter.  

Generera 1–2 filer åt gången. Mindre uppgifter ger mer exakta resultat och gör manuell validering enklare.

Playwright-specifika råd

  • Skapa aldrig flera instanser av samma Page Object utan behov.

Att göra det kan orsaka inkonsekvent tillstånd eller brutna selektorer vid navigering mellan sidor. Lösningen? Implementera en Page Manager som håller alla page objects tillgängliga via en enda instans.

Här är en minimal exempeluppsättning:

// 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();
  });

Andra tekniska tips

  • Ta bort alla ”get”-prefix från lokatorer som AI-agenten genererar — använd istället direkta lokatormetoder.
  • Glöm inte att konfigurera dotenv för miljövariabler och autentiseringsuppgifter.
  • Anpassa din Playwright-konfiguration för att mata ut alla rapporter under Playwright-mappen, så att din projektrot förblir ren:
reporter: [["html", { open: "never", outputFolder: ".‌/playwright/reports/" }]],
  outputDir: ".‌/playwright/test-results",
6

Efterord

Jag inkluderade inte ESLint-installation här eftersom det varierar från projekt till projekt. 
Den enda universella rekommendationen jag kan ge är att installera Playwright ESLint-pluginet för bättre regeltillämpning: eslint-plugin-playwright.

Om du vill utforska ett komplett fungerande exempel på denna migrering — inklusive promptfiler, tsconfigs och CI-inställningar — kan du kolla in det fullständiga repositoryt här: Exempel på repo

Kvalitetssäkring (QA)

Kommentarer

Logga in för att lämna en kommentar
Fortsätt med GoogleFortsätt med Google
Modern

Våra Fall

Innovation handlar inte bara om idéer - det handlar om utförande, att förvandla vision till verklighet och skapa lösningar som verkligen gör intryck. Se vad vi har byggt och hur det fungerar:

  • Vård
  • Media och Underhållning
  • e-handel
  • Amazon Web Services
  • Molnkostnadsoptimering
  • Serverlös applikation
  • Detaljhandel

Senaste Artiklarna