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.

Överraskande nog, med hjälp av GitHub Copilot, lyckades vi påbörja migreringen från Cypress till Playwright på bara ett par dagar – och hå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 gick tillväga med denna migrering, lärdomarna vi drog längs 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 pågick under ytan. Vår testuppsättning inkluderade:

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

Totalt hade vi 148 E2E-tester som kördes på Cypress – alla fungerade bra, men organisationen ville standardisera QA-verktygen över teamen, vilket bara betydde 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 Copilot för testautomatisering. Istället för att manuellt skriva om hundratals testfiler ville jag se hur långt vi kunde pressa AI-assistansen för att automatisera syntaxkonvertering från Cypress till Playwright.

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

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

Inom 1–2 dagar hade vi redan en fungerande prototyp av Playwrights testautomatiseringsramverk.

3

Två viktiga förbehåll

Verifiera alltid AI-genererad kod

Även om AI-assisterad kodmigrering påskyndar saker och ting måste du granska varje rad. Infrastrukturmigreringar kan lätt introducera subtila buggar eller prestandaregressioner. Tänk på Copilot som din parprogrammerare, inte en autopilot.

Håll det gamla ramverket igång till slutet

Blockera aldrig releaser eller regressioner under migreringen. Behåll dina Cypress-tester körande på CI tills motsvarande sviter är helt stabila i Playwright. När en svit är migrerad och validerad, ta bort den från Cypress och växla CI till att köra den i Playwright.

Denna gradvisa utrullning säkerställer noll nedtid 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 finns arbetsflödet som hjälpte oss att köra båda ramverken parallellt, upprätthå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 leva 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 tidsåtgång mellan Cypress och Playwright
  • Hålla CI-pipelines igång för båda

Exempel på initiering:

Initialization example.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 initierar 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 samma repo – inga fler kompileringskonflikter och ingen risk att oavsiktligt initiera delade metoder två gånger.

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

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

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 vid alla migreringar.

Hur man lägger till prompten

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

Du kan döpa den till något i stil med migrate_tests.md.

Prompt-exempel:

---
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"]');`

Varför detta hjälper

Denna typ av strukturerad prompt gör att GitHub Copilot kan agera 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 den manuella konverteringstiden 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 – sidobjekt, komponenter, hjälpare och slutligen testerna – en efter en.

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

Hur man startar migrering

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 sidobjekt) som du vill migrera

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

How to Start Migration.webpExempel på migrering av sidobjektet inventoryPage

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 utmatningen inte är perfekt. Enligt 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 fixar 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 hålla beroenden konsekventa och undvika saknade referenser, följ denna ordning:

  • Generera först icke-testfiler:
  • Sidobjekt
  • Komponenter
  • Hjälpfiler
  • Konstanter

2. När alla stödfiler är genererade och verifierade, lägg till mappen #app i chattkontexten, migrera sedan själva testfilerna.

Verifieringssteg

När ett Playwright-test genereras:

1. Granska koden – bekräfta att sidlokatörerna, teststegen och påståendena är korrekta.

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

3. Fixa 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 framgångsrikt:

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, dubbelramverksläge – där både Cypress och Playwright kördes sida vid sida. Denna struktur gjorde att vi kunde:

  • Gradvis avveckla Cypress-testsviter när de ersattes
  • Hålla CI/CD-pipelines igång 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-migreringsprompts. Genom att versionshantera dessa kan du uppdatera eller återanvända dem för framtida ramverksmigreringar eller refactoring.
  • .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.
  • playwright/-mappen — fungerar nu som den primära automatiseringskällan framöver. Den speglar strukturen från den tidigare Cypress-implementeringen för att underlätta introduktion 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 byggen.

Denna struktur stödde inte bara en smidig övergång utan positionerade även 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.

General AI-Related Advice.webp

  • Bygg om importer manuellt.

Copilot tenderar att trassla till importvägar. Det är ofta snabbare att ta bort dem och importera manuellt igen, 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 ”minns” dina projektkonventioner, vilket leder till bättre resultat.

  • Dekomponera migreringsuppgifter.  

Generera 1–2 filer i taget. Mindre uppgifter ger mer exakta resultat och gör manuell validering enklare.

Playwright-specifika råd

  • Skapa aldrig flera instanser av samma sidobjekt utan behov.

Att göra det kan orsaka inkonsekvent tillstånd eller trasiga väljare när man navigerar mellan sidor. Lösningen? Implementera en Page Manager som håller alla sidobjekt tillgängliga via en enda instans.

Här är ett minimalt exempel på en uppsä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 väljare som AI-agenten genererar – använd istället direkta lokatormetoder.
  • Glöm inte att konfigurera dotenv för miljövariabler och inloggningsuppgifter.
  • Anpassa din Playwright-konfiguration för att skriva ut alla rapporter under Playwright-mappen, så att din projektmapp förblir ren:
reporter: [["html", { open: "never", outputFolder: "./playwright/reports/" }]],
  outputDir: "./playwright/test-results",
6

Efterord

Jag inkluderade inte ESLint-konfiguration här eftersom den varierar från projekt till projekt.  
Den enda universella rekommendationen jag kan ge är att installera Playwright ESLint-pluginen 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 hela repositoryt här: Exempel-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