Logo JetBase
  • Accueil
  • Blog
  • Migration de tests Cypress vers Playwright avec Copilot
Bannière

Lorsque nous avons reçu une demande de migration des tests Cypress vers Playwright, nous nous attendions à un processus de réécriture long et fastidieux.

Étonnamment, avec l'aide de GitHub Copilot, nous avons réussi à lancer la migration de Cypress vers Playwright en seulement quelques jours — et à maintenir notre pipeline CI opérationnel tout au long du processus sans interrompre les tests.

Dans cet article, je vais partager comment nous avons abordé cette migration, les leçons que nous avons apprises en cours de route, et comment la migration de code assistée par l'IA peut réduire drastiquement la charge de travail manuelle des transitions de frameworks.

1

Contexte du projet

Le projet était une plateforme de santé complexe avec beaucoup de choses qui se passaient en coulisses. Notre configuration de test incluait :

  • Tests de bout en bout (E2E) et d'intégration
  • Une architecture TAF personnalisée avec des configurations, des nettoyages et la création d'entités
  • Configurations multi-environnements
  • Données dynamiques et logique de navigation complexe

Au total, nous avions 148 tests E2E exécutés sur Cypress — tous fonctionnaient bien, mais l'organisation souhaitait standardiser les outils QA au sein des équipes, ce qui signifiait une chose : la migration vers Playwright.

2

Pourquoi utiliser GitHub Copilot pour la migration ?

Lorsque la demande de migration est arrivée, j'ai vu une opportunité d'expérimenter l'automatisation des tests avec GitHub Copilot. Au lieu de réécrire manuellement des centaines de fichiers de test, je voulais voir jusqu'où nous pouvions pousser l'assistance de l'IA pour automatiser la conversion de syntaxe de Cypress vers Playwright.

Et honnêtement, cela a fonctionné bien mieux que prévu.

GitHub Copilot a géré la majeure partie de la traduction répétitive (sélecteurs, commandes, gestion asynchrone, etc.), nous permettant de nous concentrer sur les ajustements d'infrastructure et le réglage fin.

En 1 à 2 jours, nous avions déjà un prototype fonctionnel du framework d'automatisation des tests Playwright.

3

Deux avertissements importants

Toujours vérifier le code généré par l'IA

Même si la migration de code assistée par l'IA accélère les choses, vous devez examiner chaque ligne. Les migrations d'infrastructure peuvent facilement introduire des bugs subtils ou des régressions de performance. Considérez Copilot comme votre programmeur pair, pas comme un pilote automatique.

Maintenir l'ancien framework en fonctionnement jusqu'à la fin

Ne jamais bloquer les livraisons ou les régressions pendant la migration. Maintenez vos tests Cypress en cours d'exécution sur le CI jusqu'à ce que les suites correspondantes soient entièrement stables dans Playwright. Une fois qu'une suite est migrée et validée, retirez-la de Cypress et configurez le CI pour l'exécuter dans Playwright.

Ce déploiement progressif garantit un temps d'arrêt nul et une couverture de test continue tout au long du processus.

4

Flux de travail pour migrer de Cypress vers Playwright

Une fois les bases claires, nous sommes passés à la migration pratique.  
Voici le flux de travail qui nous a aidés à exécuter les deux frameworks en parallèle, à maintenir la stabilité du CI et à commencer la migration des tests de manière incrémentale — sans bloquer les livraisons en cours.

1. Initialiser Playwright et créer la structure des dossiers

La première étape a été d'initialiser Playwright et de créer une structure de dossiers propre et isolée. Nous voulions que les deux frameworks coexistent côte à côte dans le même dépôt, afin de pouvoir les exécuter simultanément pendant la transition.

Cette configuration nous a permis de :

  • Migrer progressivement
  • Comparer les résultats et les temps entre Cypress et Playwright
  • Maintenir les pipelines CI en fonctionnement pour les deux

Exemple d'initialisation :

Exemple d'initialisation.webp

Voici la structure de dossiers proposée que nous avons utilisée pour 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. Configurer des fichiers tsconfig séparés pour chaque framework

L'un des premiers problèmes rencontrés était les conflits TypeScript — les deux frameworks définissent des noms de méthodes similaires (expect, request, etc.), et le partage d'un seul fichier tsconfig.json entraînait des erreurs de compilation.

La solution était simple : séparer les configurations TypeScript. Cette structure garantit que chaque framework initialise ses propres définitions de types et évite les collisions de types :

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

tsconfig.json principal
Ajoutez l'option composite pour prendre en charge les références de projet.

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

Avec cette séparation en place, les deux frameworks pouvaient coexister pacifiquement dans un seul dépôt — plus de conflits de compilateur, et aucun risque d'initialiser accidentellement des méthodes partagées deux fois.

3. Créer une invite pour automatiser la migration avec GitHub Copilot

Une fois Playwright initialisé et le projet structuré, il était temps de laisser GitHub Copilot aider pour le gros du travail.

Pour ce faire, nous avons créé une invite personnalisée qui définit comment Copilot doit se comporter lors de la réécriture des tests de Cypress vers Playwright. L'invite indique à l'agent quoi faire, comment le faire et ce qu'il doit ignorer — donnant des résultats cohérents pour toutes les migrations.

Comment ajouter l'invite

Dans votre dépôt GitHub, ouvrez l'icône d'engrenage ⚙️ dans Copilot Chat et créez une nouvelle invite.  
Le fichier sera automatiquement enregistré sous : ./github/prompts/

Vous pouvez le nommer quelque chose comme migrate_tests.md.

Exemple d'invite :

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

Pourquoi cela aide

Ce type d'invite structurée permet à GitHub Copilot de se comporter davantage comme un assistant de migration spécialisé, et non comme un générateur de code à usage général.

Avec le bon contexte et les bonnes règles, Copilot peut automatiquement :

  • Réécrire de larges portions de tests Cypress en syntaxe Playwright
  • Maintenir la cohérence de la structure des dossiers et des tests 
  • Réduire le temps de conversion manuelle de 70 à 80 %

Essentiellement, vous créez votre propre chaîne d'outils de migration AI — adaptée à la structure et aux conventions de votre projet.

4. Sélectionner le mode Agent et exécuter la migration étape par étape

Une fois l'invite prête, l'étape suivante consistait à exécuter la migration avec GitHub Copilot en mode agent. Dans cette phase, nous avons laissé le modèle d'IA traiter chaque fichier — objets de page, composants, assistants et enfin les tests — un par un.

Pour cette tâche, nous avons utilisé le modèle GPT-4.1, qui offre les transformations de code les plus cohérentes et contextuellement pertinentes.

Comment démarrer la migration

1. Ouvrir Copilot Chat dans VS Code

2. Utilisez / pour sélectionner le fichier d'invite que vous avez créé précédemment

3. Utilisez # pour sélectionner un fichier spécifique (par exemple, un test Cypress ou un objet de page) que vous souhaitez migrer

Copilot appliquera maintenant votre invite de migration et générera une version du fichier compatible avec Playwright directement dans le chat.

Comment démarrer la migration.webpExemple de migration de l'objet de page 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;

Ajuster le comportement de l'Agent

Ne vous inquiétez pas si la première sortie n'est pas parfaite. D'après notre expérience, l'agent IA obtient généralement 60 à 70 % du code correct dès la première tentative. L'essentiel est de maintenir le contexte de conversation actif — continuez à affiner le même fil de discussion en demandant des corrections ou des ajustements. Ce faisant, Copilot commence à apprendre les modèles de votre projet et produit un code plus précis et réutilisable.

Essentiellement, plus vous effectuez d'itérations dans le même chat, meilleure est la qualité de la migration.

Ordre de migration : Fichiers d'abord, puis tests

Pour maintenir la cohérence des dépendances et éviter les références manquantes, suivez cet ordre :

  • 1. Générez d'abord les fichiers non-test :
    • Objets de page
    • Composants
    • Fichiers d'aide
    • Constantes
  • 2. Une fois tous les fichiers de support générés et vérifiés, ajoutez le dossier #app au contexte du chat, puis migrez les fichiers de test eux-mêmes.

Étape de vérification

Lorsqu'un test Playwright est généré :

  • 1. Examinez le code — confirmez que les localisateurs de page, les étapes de test et les assertions sont corrects.
  • 2. Exécutez le test à l'aide de votre CLI Playwright pour vous assurer qu'il s'exécute correctement.
  • 3. Corrigez les légères erreurs de syntaxe ou les incohérences de fixtures si Copilot a mal interprété une commande Cypress.

En vérifiant après chaque fichier, vous maintenez la migration stable et incrémentale, évitant un débogage à grande échelle plus tard.

Exemple de test inventoryPage migré qui passe avec succès :

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. Structure finale du dépôt après la migration

Une fois la migration terminée, notre dépôt a atteint un état stable et à double framework — exécutant entièrement Cypress et Playwright côte à côte. Cette structure nous a permis de :

  • Déprécier progressivement les suites de tests Cypress au fur et à mesure de leur remplacement
  • Maintenir les pipelines CI/CD opérationnels pour les deux frameworks
  • Maintenir une architecture propre et modulaire

Voici la structure finale après avoir terminé le flux de travail de migration.

Structure finale des dossiers

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

Notes sur la structure

  • .github/prompts/ — contient vos invites de migration Copilot. Les conserver versionnées vous permet de les mettre à jour ou de les réutiliser pour de futures migrations de frameworks ou des refactorisations.
  • .github/workflows/ — contient vos définitions CI/CD. Vous pouvez exécuter les tâches Playwright et Cypress en parallèle jusqu'à la dépréciation complète de l'ancien framework.
  • Dossier playwright/ — agit désormais comme la source d'automatisation principale. Il reflète la structure de l'implémentation Cypress précédente pour faciliter l'intégration et assurer la cohérence.
  • tsconfig.playwright.json et tsconfig.cypress.json — restent séparés pour une isolation complète des types, évitant les conflits lors des builds.

Cette structure a non seulement soutenu une transition en douceur, mais a également positionné le projet pour une évolutivité future, une flexibilité CI et une propriété modulaire des tests au sein des équipes.

5

Conseils pratiques et leçons apprises

Après avoir migré près de 150 tests avec Copilot et Playwright, voici quelques leçons clés et conseils pratiques qui ont rendu le processus plus fluide (et nous ont épargné quelques maux de tête).

Conseils généraux liés à l'IA

  • L'IA ment — souvent de manière convaincante.  

Vérifiez toujours chaque ligne de code générée par l'IA. Ne lui faites jamais confiance aveuglément, surtout lors des migrations d'infrastructure.

  • Respectez des conventions de nommage cohérentes.

Gardez des noms de variables et de méthodes identiques entre les frameworks — cela aide l'agent IA à migrer le code plus précisément et vous facilite la tâche pour les trouver et les importer plus tard.

Conseils généraux liés à l'IA.webp

  • Reconstruisez les imports manuellement.

Copilot a tendance à foirer les chemins d'importation. Il est souvent plus rapide de les supprimer et de les réimporter manuellement, ou de configurer des imports globaux comme : import x from "playwright/foo/bar";

  • Réutilisez le même chat pour toutes les migrations.

Restez sur un seul fil de discussion Copilot — cela construit le contexte et « se souvient » des conventions de votre projet, menant à de meilleurs résultats.

  • Décomposez les tâches de migration.  

Générez 1 à 2 fichiers à la fois. Des tâches plus petites donnent des résultats plus précis et facilitent la validation manuelle.

Conseils spécifiques à Playwright

  • Ne créez jamais plusieurs instances du même objet de page sans nécessité.

Cela peut entraîner un état incohérent ou des sélecteurs cassés lors de la navigation entre les pages. La solution ? Implémentez un gestionnaire de pages qui maintient tous les objets de page accessibles via une seule instance.

Voici une configuration d'exemple minimale :

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

Autres conseils techniques

  • Supprimez tous les préfixes « get » des localisateurs que l'agent IA génère — utilisez plutôt des méthodes de localisateur directes.
  • N'oubliez pas de configurer dotenv pour les variables d'environnement et les identifiants.
  • Personnalisez votre configuration Playwright pour que tous les rapports soient générés dans le dossier Playwright, afin que la racine de votre projet reste propre :
reporter: [["html", { open: "never", outputFolder: ".‌/playwright/reports/" }]],
  outputDir: ".‌/playwright/test-results",
6

Postface

Je n'ai pas inclus la configuration d'ESLint ici car elle varie d'un projet à l'autre.  
La seule recommandation universelle que je puisse faire est d'installer le plugin ESLint de Playwright pour une meilleure application des règles : eslint-plugin-playwright.

Si vous souhaitez explorer un exemple complet et fonctionnel de cette migration — incluant les fichiers d'invite, les tsconfigs et la configuration CI — vous pouvez consulter le dépôt complet ici : Exemple de dépôt

Assurance Qualité (AQ)

Commentaires

Connectez-vous pour laisser un commentaire
Continuer avec GoogleContinuer avec Google
Moderne

Nos Cas

L'innovation ne concerne pas seulement les idées - il s'agit de l'exécution, de transformer la vision en réalité et de créer des solutions qui ont vraiment un impact. Voyez ce que nous avons construit et comment cela fonctionne :

  • Soins de santé
  • Médias et Divertissement
  • eCommerce
  • Amazon Web Services
  • Optimisation des coûts cloud
  • Application sans serveur
  • Vente au détail

Derniers Articles