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

Lorsque nous avons reçu une demande de migration de 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, tout en maintenant notre pipeline CI en fonctionnement sans interrompre aucun processus de test.

Dans cet article, je partagerai la façon dont nous avons abordé cette migration, les leçons que nous avons tirées 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 d'éléments sous le capot. 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 fonctionnant parfaitement, mais l'organisation souhaitait standardiser les outils d'assurance qualité (QA) entre les é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 avec l'automatisation des tests 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 syntaxique de Cypress vers Playwright.

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

GitHub Copilot a géré la plupart des traductions répétitives (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 clés

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 bloquez jamais les releases ou les régressions pendant la migration. Maintenez vos tests Cypress en fonctionnement sur la 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 la CI pour l'exécuter dans Playwright.

Ce déploiement progressif garantit zéro temps d'arrêt 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 établies, nous sommes passés à la migration pratique.  
Vous trouverez ci-dessous le flux de travail qui nous a aidés à exécuter les deux frameworks en parallèle, à maintenir la stabilité de la CI et à commencer à migrer les tests de manière incrémentielle, sans bloquer les releases en cours.

1. Initialiser Playwright et créer la structure de 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 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 que nous avons 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 a entraîné 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 type et évite les collisions de types :

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

Main tsconfig.json
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"]
}

Grâce à cette séparation, les deux frameworks pouvaient coexister pacifiquement dans un seul dépôt – plus de conflits de compilateur, et aucun risque d'initialiser accidentellement deux fois des méthodes partagées.

3. Créer un prompt pour automatiser la migration avec GitHub Copilot

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

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

Comment ajouter le prompt

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

Vous pouvez le nommer, par exemple, migrate_tests.md.

Exemple de prompt :

---
mode: agent
---

Vous êtes un générateur de tests Playwright, un expert en automatisation de navigateur et en tests de bout en bout.
Votre spécialité est la création de tests Playwright robustes et fiables qui simulent avec précision les interactions utilisateur et valident le comportement de l'application.

Votre tâche est d'aider à migrer les tests Cypress existants vers des tests Playwright.
Les tests Cypress, les objets de page, les composants et les fichiers utilitaires sont fournis en entrée, et vous devez générer un code de test Playwright équivalent.
Affichez TOUT le code migré dans le chat.

Lors de la génération du code de test Playwright ou de tout objet de page, assurez-vous que :
1. La structure du test suit la même structure que les tests Cypress.
2. Créez un "fixture" global pour l'initialisation (setup) et la finalisation (teardown) et réutilisez-le dans les tests.
3. Toutes les interactions utilisateur (clics, saisie, navigation) sont traduites avec précision en syntaxe Playwright dans les objets de page et les fichiers de test.
4. Les assertions Cypress sont converties en assertions Playwright équivalentes.
5. Ignorez les parties d'espionnage et de "stubbing" réseau des tests Cypress.
6. Commentez toutes les méthodes avec des attentes (par exemple, cy.wait) dans le code Playwright.
7. N'utilisez pas de méthodes "get" dans les objets de page ; utilisez plutôt des méthodes directes `backbackItem = () => this.page.locator('[id="item_4_title_link"]');`

Pourquoi cela aide

Ce type de prompt structuré 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 grands blocs 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 le prompt prêt, 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, utilitaires et enfin les tests — un par un.

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

Comment démarrer la migration

1. Ouvrez Copilot Chat dans VS Code

2. Utilisez / pour sélectionner le fichier de prompt 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 prompt 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;

Affiner le comportement de l'agent

Ne vous inquiétez pas si le premier résultat n'est pas parfait. D'après notre expérience, l'agent IA obtient généralement 60 à 70 % du code correct dès la première tentative. La clé 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 schémas 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 utilitaires
  • 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 erreurs de syntaxe mineures ou les incompatibilités de "fixture" 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 ainsi un débogage à grande échelle plus tard.

Exemple de test inventoryPage migré et réussi :

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 migration

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

  • Déprécier progressivement les suites de tests Cypress à mesure qu'elles étaient remplacées
  • Maintenir les pipelines CI/CD opérationnels pour les deux frameworks
  • Conserver une architecture propre et modulaire

Vous trouverez ci-dessous 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 prompts de migration Copilot. Les conserver versionnés vous permet de les mettre à jour ou de les réutiliser pour de futures migrations de frameworks ou 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 appropriation modulaire des tests par les é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 évité 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 les 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 retrouver et les importer plus tard.

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

  • Reconstruisez manuellement les imports.

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

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

Tenez-vous-en à un seul fil de discussion Copilot — cela construit le contexte et « mémorise » les conventions de votre projet, ce qui conduit à 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 raison.

Cela peut entraîner un état incohérent ou des sélecteurs cassés lors de la navigation entre les pages. La solution ? Mettre en œuvre un gestionnaire de pages (Page Manager) qui maintient tous les objets de page accessibles via une seule instance.

Voici un exemple de configuration 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 afficher tous les rapports 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 de prompt, 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