Når vi fikk en forespørsel om å migrere Cypress-tester til Playwright, forventet vi en lang, tidkrevende omskrivingsprosess.
Overraskende nok, med hjelp fra GitHub Copilot, klarte vi å starte migreringen fra Cypress til Playwright på bare noen dager — og holde CI-pipelinen kjørende hele tiden uten å forstyrre noen testprosesser.
I denne artikkelen vil jeg dele hvordan vi nærmet oss denne migreringen, hvilke lærdommer vi fikk underveis, og hvordan AI-assistert kode-migrering kan drastisk redusere den manuelle overhodet ved rammeverksoverganger.
Prosjektkontekst
Prosjektet var en kompleks helseservieplattform med mye som foregikk under panseret. Vår testoppsett inkluderte:
- End-to-end (E2E) og integrasjonstester
- Et tilpasset TAF-arkitektur med oppsett, nedleggelse og enhetsopprettelse
- Multi-miljø konfigurasjoner
- Dynamiske data og dyp navigasjonslogikk
Totalt hadde vi 148 E2E-tester som kjørte på Cypress — alle fungerte fint, men organisasjonen ønsket å standardisere QA-verktøy på tvers av teamene, noe som betydde én ting: migrering til Playwright.
Hvorfor Use GitHub Copilot for Migrering?
Når forespørselen om migrering kom inn, så jeg en mulighet til å eksperimentere med GitHub Copilot for testautomatisering. I stedet for å omskrive hundrevis av testfiler manuelt, ønsket jeg å se hvor langt vi kunne presse AI-assistansen for å automatisere syntaksomformingen fra Cypress til Playwright.
Og ærlig talt, det fungerte langt bedre enn forventet.
GitHub Copilot håndterte de fleste av de gjentagende oversettelsene (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 Ansvarsfraskrivelser
Verifiser Alltid AI-Generert Kode
Selv om AI-assistert kode-migrering får ting til å gå raskere, må du gjennomgå hver linje. Infrastrukturmigreringer kan lett introdusere subtile feil eller ytelsesregresjoner. Tenk på Copilot som din parprogrammerer, ikke som autopilot.
Hold Det Gamle Rammeverket Kjørende Til Slutt
Aldri blokkere utgivelser eller regresjoner under migrering. Hold Cypress-testene dine kjørende på CI inntil de tilsvarende suiteene er helt stabile i Playwright. Når en suite er migrert og validert, fjern den fra Cypress og bytt CI for å 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 grunnlaget var klart, gikk vi videre til den praktiske migreringen.
Nedenfor er arbeidsflyten som hjalp oss å kjøre begge rammeverkene parallelt, opprettholde CI-stabilitet, og starte migreringen av tester inkrementelt — uten å blokkere pågående utgivelser.
1. Initialiser Playwright og Opprett Mappen Struktur
Det første steget var å initialisere Playwright og opprette en ren, isolert mappestruktur. Vi ønsket at begge rammeverkene skulle leve side om side i det samme depotet, slik at vi kunne kjøre dem samtidig under overgangen.
Den oppsetningen tillot oss å:
- Migrere gradvis
- Sammenligne resultater og tider mellom Cypress og Playwright
- Holde CI-pipelines kjørende for begge
Eksempel på initialisering:

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 stod overfor var TypeScript-konflikter — begge rammeverkene definerer lignende metodenavn (expect, request, osv.), og deling av en enkelt tsconfig.json førte til kompilasjonsfeil.
Løsningen var enkel: del TypeScript-konfigurasjonene. Denne strukturen sikrer at hvert rammeverk initierer sine egne typebetegnelser og unngår typekonflikter:
├── 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 sameksistere fredelig i ett repo — ikke flere kompilatorkonflikter, og ingen risiko for å ved et uhell initialisere delte metoder to ganger.
3. Opprett en prompt for å automatisere migrering med GitHub Copilot
Når Playwright var initialisert og prosjektet var strukturert, var det på tide å la GitHub Copilot hjelpe med det tunge løftet.
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 som skal ignoreres — noe som gir konsistente resultater på tvers av alle migreringer.
Slik legger du til prompten
Inne i ditt GitHub-repositorium, åpne tannhjulikonet ⚙️ i Copilot Chat og opprett en ny prompt.
Filene lagres automatisk under: ./github/prompts/
Du kan navngi den noe som migrate_tests.md.
Eksempel på prompt:
---
mode: agent
---
Du er en Playwright Test Generator, en ekspert på nettleserautomatisering og ende-til-ende-testing.
Din spesialitet er å lage robuste, pålitelige Playwright-tester som nøyaktig simulerer brukerinteraksjoner og validerer applikasjonsatferd.
Din oppgave er å hjelpe med å migrere eksisterende Cypress-tester til Playwright-tester.
Cypress-testene, sideobjektene, komponentene og hjelpefilene er gitt som input, og du må generere tilsvarende Playwright-testkode.
Output ALL migrert kode til chatten.
Når du genererer Playwright-testkode eller noen sideobjekter, må du sikre at:
1. Teststrukturen følger den samme strukturen som Cypress-testene.
2. Opprett en global fixture for oppsett og nedsetting og gjenbruk den i testene.
3. Alle brukerinteraksjoner (klikk, skriving, navigasjon) oversettes nøyaktig til Playwright-syntaks i sideobjekter og testfiler.
4. Påstander i Cypress konverteres til tilsvarende Playwright-påstander.
5. Ignorer nettverksinnsyning og stubbing-deler av Cypress-testene.
6. Kommenter ut alle metodene med venting (f.eks. cy.wait) i Playwright-koden.
7. Ikke bruk get-metoder i sideobjekter; bruk direkte metoder i stedet `backbackItem = () => this.page.locator('[id="item_4_title_link"]');`Hvorfor dette hjelper
Denne typen strukturerte prompt gjør at GitHub Copilot kan oppføre seg mer som en spesialisert migreringsassistent, ikke en generell kodegenerator.
Med riktig kontekst og regler kan Copilot automatisk:
- Omskrive store biter av Cypress-tester til Playwright-syntaks
- Opprettholde konsistens i mappe- og teststruktur
- Redusere manuell konverteringstid med 70–80%
I hovedsak lager du ditt eget AI-migreringsverktøy — skreddersydd for prosjektets struktur og konvensjoner.
4. Velg agentmodus og kjør migreringen trinn for trinn
Når prompten var klar, var neste steg å faktisk kjøre migreringen med GitHub Copilot i agentmodus. I denne fasen lar vi AI-modellen prosessere hver fil — sideobjekter, komponenter, hjelpere, og til slutt testene — én etter én.
For this task, we used the GPT-4.1-modellen, som leverer de mest konsistente og kontekstsensitive kodeforvandlinger.
Slik starter du migrasjonen
1. Åpne Copilot Chat i VS Code
2. Bruk / for å velge prosjektfilen du opprettet tidligere
3. Bruk # for å velge en spesifikk fil (for eksempel en Cypress-test eller sideobjekt) som du ønsker å migrere
Copilot vil nå bruke migreringsprompten din og generere en Playwright-kompatibel versjon av filen direkte i chatten.
Eksempel på migrering av inventoryPage Sideobjekt
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;Justering av agentens atferd
Ikke bekymre deg hvis det første resultatet ikke er perfekt. Vår erfaring er at AI-agenten vanligvis får 60–70 % av koden riktig på første forsøk. Nøkkelen er å holde samtalekonteksten aktiv — fortsett å forbedre resultatet i den samme chattråden ved å be om rettelser eller justeringer. Etter hvert begynner Copilot å lære mønstrene i prosjektet ditt og produserer mer nøyaktig og gjenbrukbar kode.
Kort sagt: jo flere iterasjoner du gjør i den samme chatten, desto bedre blir kvaliteten på migreringen.
Migreringsrekkefølge: filer først, deretter tester
For å holde avhengighetene konsistente og unngå manglende referanser, følg denne rekkefølgen:
1. Generer filene som ikke er tester, først:
- Sideobjekter
- Komponenter
- Hjelpefiler
- Konstanter
2. Når alle støttefilene er generert og verifisert, legger du til #app-mappen i chatkonteksten og migrerer deretter selve testfilene.
Verifiseringstrinn
Når en Playwright-test er generert:
1. Gå gjennom koden — bekreft at sidelokatorer, testtrinn og påstander er riktige.
2. Kjør testen med Playwright CLI for å sikre at den kjører som den skal.
3. Rett opp mindre avvik i syntaks eller fixtures hvis Copilot har misforstått en Cypress-kommando.
Ved å verifisere etter hver fil holder du migreringen stabil og trinnvis, og du unngår omfattende feilsøking senere.
Eksempel på en migrert inventoryPage-test som består:
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 migreringen
Da migreringen var fullført, hadde repositoryet vårt nådd en stabil tilstand med to rammeverk — Cypress og Playwright kjørte fullt ut side om side. Denne strukturen gjorde det mulig for oss å:
- Gradvis fase ut Cypress-testsuiter etter hvert som de ble erstattet
- Holde CI/CD-pipelinene i drift for begge rammeverkene
- Opprettholde en ren og modulær arkitektur
Nedenfor er den endelige strukturen etter at migreringsarbeidsflyten var fullført.
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 Copilot-promptene for migreringen. Når de er versjonert, kan du oppdatere eller gjenbruke dem ved fremtidige rammeverksmigreringer eller refaktoreringer..github/workflows/— inneholder CI/CD-definisjonene dine. Du kan kjøre Playwright- og Cypress-jobber parallelt til det gamle rammeverket er helt faset ut.playwright/-mappen — fungerer nå som den primære kilden for automatisering. Den speiler strukturen i den tidligere Cypress-implementeringen for å gjøre det enklere å komme i gang og sikre konsistens.tsconfig.playwright.jsonogtsconfig.cypress.json— holdes adskilt for full typeisolasjon og forhindrer konflikter under bygg.
Denne strukturen ga ikke bare en smidig overgang, men gjorde også prosjektet klart for fremtidig skalerbarhet, CI-fleksibilitet og modulært testeierskap på tvers av team.
Flytt testsuiten din uten å forstyrre utgivelser eller miste dekning. QA-ingeniørene våre kan hjelpe deg med å planlegge migreringen, tilpasse automatiseringsarkitekturen og holde begge rammeverkene stabile under overgangen.
Praktiske råd & erfaringer
Etter å ha migrert nesten 150 tester med Copilot og Playwright, er her noen viktige erfaringer og praktiske tips som gjorde prosessen smidigere (og sparte oss for en del hodebry).
Generelle råd om AI
- AI lyver — ofte overbevisende.
Kontroller alltid hver kodelinje AI-en genererer. Stol aldri blindt på den, spesielt ikke under infrastrukturmigreringer.
- Hold deg til konsistente navnekonvensjoner.
Bruk identiske variabel- og metodenavn i begge rammeverkene — det hjelper AI-agenten med å migrere koden mer nøyaktig og gjør det lettere for deg å finne og importere dem senere.

- Bygg importene på nytt manuelt.
Copilot har en tendens til å rote til importstiene. Ofte går det raskere å slette dem og importere på nytt manuelt, eller å sette opp globale importer som: import x from "playwright/foo/bar";
- Gjenbruk den samme chatten for alle migreringer.
Hold deg til én Copilot-chattråd — den bygger opp kontekst og «husker» konvensjonene i prosjektet ditt, noe som gir bedre resultater.
- Del opp migreringsoppgavene.
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.
Det kan føre til inkonsistent tilstand eller ødelagte selektorer når du navigerer mellom sider. Løsningen? Implementer en Page Manager som gjør alle sideobjekter tilgjengelige via én 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 lokatorene AI-agenten genererer — bruk direkte lokatormetoder i stedet.
- Ikke glem å konfigurere
dotenvfor miljøvariabler og påloggingsinformasjon. - Tilpass Playwright-konfigurasjonen slik at alle rapporter havner i Playwright-mappen og prosjektroten holdes ryddig:
reporter: [["html", { open: "never", outputFolder: "./playwright/reports/" }]],
outputDir: "./playwright/test-results",
Etterord
Jeg tok ikke med ESLint-oppsettet her, siden det varierer fra prosjekt til prosjekt.
Den eneste universelle anbefalingen jeg kan gi, er å installere Playwright-pluginen for ESLint for bedre regelhåndheving: eslint-plugin-playwright.
Hvis du vil utforske et komplett, fungerende eksempel på denne migreringen — inkludert promptfiler, tsconfig-filer og CI-oppsett — kan du se hele repositoryet her: Repo-eksempel.
Enten du migrerer fra Cypress, bygger opp igjen en ustabil testsuite eller forbedrer QA-automatiseringen, kan JetBase hjelpe deg med overgangen med mindre manuelt arbeid og lavere leveringsrisiko.















