Da vi fikk en forespørsel om å migrere Cypress-tester til Playwright, forventet vi en lang og kjedelig omskrivningsprosess.
Overraskende nok, med hjelp fra GitHub Copilot, klarte vi å starte Cypress til Playwright-migreringen på bare et par dager – og holde CI-pipelinen vår i gang hele tiden uten å avbryte noen testprosesser.
I denne artikkelen vil jeg dele hvordan vi nærmet oss denne migreringen, lærdommene vi tok med oss, og hvordan AI-assistert kodemigrering drastisk kan redusere det manuelle arbeidet med rammeverksoverganger.
Prosjektkontekst
Prosjektet var en kompleks helseplattform med mye som foregikk under panseret. Vårt testoppsett inkluderte:
- Ende-til-ende (E2E) og integrasjonstester
- En tilpasset TAF-arkitektur med oppsett, nedrivninger og enhetsopprettelse
- Multi-miljøkonfigurasjoner
- Dynamiske data og dyp navigeringslogikk
Totalt hadde vi 148 E2E-tester som kjørte på Cypress – alle fungerte fint, men organisasjonen ønsket å standardisere QA-verktøy på tvers av team, noe som betydde én ting: Playwright-migrering.
Hvorfor bruke GitHub Copilot for migrering?
Da migreringsforespørselen kom inn, så jeg en mulighet til å eksperimentere med GitHub Copilot testautomatisering. I stedet for å manuelt skrive om hundrevis av testfiler, ønsket jeg å se hvor langt vi kunne presse AI-assistanse for å automatisere syntakskonvertering fra Cypress til Playwright.
Og ærlig talt, det fungerte mye bedre enn forventet.
GitHub Copilot håndterte det meste av den repeterende oversettelsen (selektorer, kommandoer, asynkron håndtering osv.), noe som lot oss fokusere på infrastrukturjusteringer og finjustering.
Innen 1–2 dager hadde vi allerede en fungerende prototype av Playwright-testautomatiseringsrammeverket.
To viktige forbehold
Verifiser alltid AI-generert kode
Selv om AI-assistert kodemigrering fremskynder ting, må du gjennomgå hver eneste linje. Infrastrukturmigreringer kan lett introdusere subtile feil eller ytelsesforringelser. Tenk på Copilot som din parprogrammerer, ikke en autopilot.
Hold det gamle rammeverket kjørende til slutten
Blokker aldri utgivelser eller regresjoner under migrering. Hold Cypress-testene dine kjørende på CI til de tilsvarende suitene er fullt stabile i Playwright. Når en suite er migrert og validert, fjerner du den fra Cypress og bytter CI til å kjøre den i Playwright.
Denne gradvise utrullingen sikrer null nedetid og kontinuerlig testdekning gjennom hele prosessen.
Arbeidsflyt for å migrere fra Cypress til Playwright
Når grunnarbeidet var klart, gikk vi over til den praktiske migreringen.
Nedenfor er arbeidsflyten som hjalp oss med å kjøre begge rammeverkene parallelt, opprettholde CI-stabilitet og starte inkrementell migrering av tester – uten å blokkere pågående utgivelser.
1. Initialiser Playwright og opprett mappestrukturen
Det første trinnet var å initialisere Playwright og opprette en ren, isolert mappestruktur. Vi ønsket at begge rammeverkene skulle eksistere side om side i samme repository, slik at vi kunne kjøre dem samtidig under overgangen.
Dette oppsettet tillot oss å:
- Migrere gradvis
- Sammenligne resultater og tider mellom Cypress og Playwright
- Holde CI-pipelines kjørende for begge
Initialiseringseksempel:

Her er den foreslåtte mappestrukturen vi brukte for 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.json2. Konfigurer separate tsconfig-filer for hvert rammeverk
Et av de tidlige problemene vi møtte var TypeScript-konflikter – begge rammeverkene definerer lignende metodenavn (expect, request osv.), og deling av én enkelt tsconfig.json førte til kompileringsfeil.
Løsningen var enkel: del opp TypeScript-konfigurasjonene. Denne strukturen sikrer at hvert rammeverk initialiserer sine egne typedefinisjoner og unngår typekollisjoner:
├── tsconfig.json
├── tsconfig.cypress.json
├── tsconfig.playwright.jsonHoved tsconfig.json
Legg til composite-alternativet for å støtte prosjektreferanser.
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 denne separasjonen på plass kunne begge rammeverkene eksistere fredelig i ett repo – ingen flere kompileringskonflikter, og ingen risiko for å ved et uhell initialisere delte metoder to ganger.
3. Opprett en prompt for å automatisere migrering med GitHub Copilot
Etter at Playwright var initialisert og prosjektet strukturert, var det på tide å la GitHub Copilot hjelpe til med det tunge arbeidet.
For å gjøre dette, opprettet vi en tilpasset prompt som definerer hvordan Copilot skal oppføre seg når den omskriver tester fra Cypress til Playwright. Prompten forteller agenten hva den skal gjøre, hvordan den skal gjøre det, og hva den skal ignorere – noe som gir konsistente resultater på tvers av alle migreringer.
Slik legger du til prompten
I GitHub-repositoriet ditt åpner du tannhjulikonet ⚙️ i Copilot Chat og oppretter en ny prompt.
Filen lagres automatisk under: ./github/prompts/
Du kan navngi den noe som migrate_tests.md.
Eksempel på prompt:
---
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"]');`Hvorfor dette hjelper
Denne typen strukturert prompt lar GitHub Copilot oppføre seg mer som en spesialisert migreringsassistent, ikke en generelt kodegenerator.
Med riktig kontekst og regler kan Copilot automatisk:
- Omskrive store deler av Cypress-tester til Playwright-syntaks
- Opprettholde konsistens i mappe- og teststrukturen
- Redusere manuell konverteringstid med 70–80 %
I hovedsak skaper du din egen AI-migreringsverktøykjede – skreddersydd til prosjektets struktur og konvensjoner.
4. Velg agentmodus og kjør migreringen trinn for trinn
Når prompten var klar, var neste trinn å faktisk kjøre migreringen med GitHub Copilot i agentmodus. I denne fasen lot vi AI-modellen behandle hver fil – page objects, komponenter, hjelpere og til slutt testene – én etter én.
For denne oppgaven brukte vi GPT-4.1-modellen, som leverer de mest konsistente og kontekstbevisste kodeomformasjonene.
Slik starter du migrering
1. Åpne Copilot Chat i VS Code
2. Bruk / for å velge promptfilen du opprettet tidligere
3. Bruk # for å velge en spesifikk fil (for eksempel en Cypress-test eller et page object) som du vil migrere
Copilot vil nå anvende migreringsprompten din og generere en Playwright-kompatibel versjon av filen direkte i chatten.
Eksempel 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;Finjustering av agentens oppførsel
Ikke bekymre deg om det første resultatet ikke er perfekt. Fra vår erfaring får AI-agenten vanligvis 60–70 % av koden riktig på første forsøk. Nøkkelen er å holde samtalekonteksten aktiv – fortsett å forbedre den samme chat-tråden ved å be om rettelser eller justeringer. Når du gjør dette, begynner Copilot å lære prosjektmønstrene dine og produserer mer nøyaktig, gjenbrukbar kode.
I hovedsak, jo flere iterasjoner du gjør i den samme chatten, jo bedre blir migreringskvaliteten.
Migreringsrekkefølge: Filer først, deretter tester
For å holde avhengigheter konsistente og unngå manglende referanser, følg denne rekkefølgen:
- Page Objects
- Komponenter
- Hjelpefiler
- Konstanter
2. Når alle støttefiler er generert og verifisert, legg til #app-mappen i chat-konteksten, og migrer deretter selve testfilene.
Verifiseringstrinn
Når en Playwright-test er generert:
- Gå gjennom koden – bekreft at side lokatorer, testtrinn og påstander er korrekte.
- Kjør testen ved hjelp av Playwright CLI for å sikre at den utføres riktig.
- Fiks mindre syntaks- eller fixture-uoverensstemmelser hvis Copilot misforsto en Cypress-kommando.
Ved å verifisere etter hver fil, holder du migreringen stabil og inkrementell, og unngår storskala feilsøking senere.
Eksempel på migrert inventoryPage-test som er vellykket bestått:
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. Endelig repositorystruktur etter migrering
Etter at migreringen var fullført, nådde repositoriet vårt en stabil, dual-rammeverkstilstand – som fullt ut kjørte både Cypress og Playwright side om side. Denne strukturen tillot oss å:
- Gradvis avvikle Cypress-testpakker etter hvert som de ble erstattet
- Holde CI/CD-pipelines operative for begge rammeverkene
- Opprettholde en ren og modulær arkitektur
Nedenfor er den endelige strukturen etter fullført migreringsarbeidsflyt.
Endelig mappestruktur
├── .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/
Merknader om strukturen
.github/prompts/– inneholder dine Copilot-migreringsprompter. Å holde disse versjonskontrollert lar deg oppdatere eller gjenbruke dem for fremtidige rammeverksmigreringer eller refaktoreringer..github/workflows/– inneholder dine CI/CD-definisjoner. Du kan kjøre både Playwright- og Cypress-jobber parallelt frem til full avvikling av det gamle rammeverket.playwright/-mappen – fungerer nå som den primære automatiseringskilden fremover. Den speiler strukturen til den tidligere Cypress-implementeringen for å lette onboarding og sikre konsistens.tsconfig.playwright.jsonogtsconfig.cypress.json– forblir separate for full typeisolering, noe som forhindrer konflikter under bygging.
Denne strukturen støttet ikke bare en smidig overgang, men posisjonerte også prosjektet for fremtidig skalerbarhet, CI-fleksibilitet og modulært testansvar på tvers av team.
Praktiske råd og lærdom
Etter å ha migrert nesten 150 tester med Copilot og Playwright, er her noen viktige lærdommer og praktiske tips som gjorde prosessen smidigere (og sparte oss for noen hodepiner).
Generelle AI-relaterte råd
- AI lyver – ofte overbevisende.
Verifiser alltid hver linje med kode som AI genererer. Stol aldri blindt på den, spesielt under infrastrukturmigreringer.
- Hold deg til konsekvente navnekonvensjoner.
Bruk identiske variabel- og metodenavn på tvers av rammeverk – det hjelper AI-agenten med å migrere kode mer nøyaktig og gjør det enklere for deg å finne og importere dem senere.

- Bygg opp importer manuelt.
Copilot har en tendens til å rote til importstier. Det er ofte raskere å slette dem og importere manuelt på nytt, eller sette opp globale importer som: import x from "playwright/foo/bar";
- Gjenbruk den samme chatten for alle migreringer.
Hold deg til én enkelt Copilot-chat-tråd – den bygger kontekst og "husker" prosjektkonvensjonene dine, noe som fører til bedre resultater.
- Del opp migreringsoppgaver.
Generer 1–2 filer om gangen. Mindre oppgaver gir mer nøyaktige resultater og gjør manuell validering enklere.
Playwright-spesifikke råd
- Opprett aldri flere instanser av det samme Page Object uten behov.
Å gjøre det kan forårsake inkonsekvent tilstand eller ødelagte selektorer når du navigerer mellom sider. Løsningen? Implementer en Page Manager som holder alle page objects tilgjengelige gjennom en enkelt instans.
Her er et minimalt eksempeloppsett:
// 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();
});Andre tekniske tips
- Fjern alle "get"-prefikser fra lokatorer som AI-agenten genererer – bruk direkte lokator-metoder i stedet.
- Ikke glem å konfigurere
dotenvfor miljøvariabler og legitimasjon. - Tilpass din Playwright-konfigurasjon for å legge ut alle rapporter under Playwright-mappen, slik at prosjektets rotmappe forblir ren:
reporter: [["html", { open: "never", outputFolder: "./playwright/reports/" }]],
outputDir: "./playwright/test-results",
Etterord
Jeg inkluderte ikke ESLint-oppsett her siden det varierer fra prosjekt til prosjekt.
Den eneste universelle anbefalingen jeg kan gi er å installere Playwright ESLint-pluginen for bedre regelhåndhevelse: eslint-plugin-playwright.
Hvis du ønsker å utforske et komplett fungerende eksempel på denne migreringen – inkludert prompt-filer, tsconfigs og CI-oppsett – kan du sjekke ut hele repositoriet her: Eksempel på repo















