Ce qui change vraiment
Trois différences de fond expliquent presque toutes les réécritures.
- Tout est asynchrone. Chaque appel Playwright renvoie une
Task:ClickAsync(),FillAsync(),TitleAsync(). Une méthode qui en appelle un devientasync Task, puis ses appelants aussi, du test jusqu'aux page objects et aux utilitaires. - Playwright attend tout seul. Avant un clic, il vérifie que l'élément est visible, stable, qu'il reçoit les événements et qu'il est activé ; avant un
FillAsync, qu'il est visible, activé et modifiable. Si ces conditions ne sont pas réunies dans le délai, l'action échoue par dépassement de délai. - Un localisateur n'est pas un élément. Un
ILocatorest réévalué à chaque usage : l'exception « élément périmé » de Selenium (StaleElementReferenceException) n'a plus d'équivalent. En contrepartie, les localisateurs sont stricts : une action lève une exception si plusieurs éléments correspondent.
Sélecteurs : de By à Locator
| Selenium | Playwright .NET |
|---|---|
By.Id("panier") | Page.Locator("#panier") |
By.CssSelector(s) | Page.Locator(s) |
By.XPath(x) | Page.Locator("xpath=" + x) |
By.Name("q") | Page.Locator("[name=\"q\"]") |
By.ClassName("c") | Page.Locator(".c") |
By.LinkText(t) | Page.Locator("a:text-is(\"t\")"), ou GetByRole(AriaRole.Link, new() { Name = t }) |
FindElement(by) | Page.Locator(sel).First si plusieurs éléments peuvent correspondre |
FindElements(by) | Page.Locator(sel).AllAsync() ; Count devient CountAsync(), [n] devient Nth(n) |
Une conversion mécanique garde vos sélecteurs CSS et XPath, ce qui permet de migrer sans changer le comportement. La documentation de Playwright recommande ensuite les localisateurs intégrés (GetByRole, GetByLabel, GetByText, GetByPlaceholder, GetByTestId…), plus robustes aux changements du DOM que les chaînes CSS ou XPath. Faites ce second passage plus tard, page object par page object.
Actions et lectures
| Selenium | Playwright .NET | À vérifier |
|---|---|---|
Click(), Clear() | ClickAsync(), ClearAsync() | |
SendKeys("texte") | FillAsync("texte") | FillAsync remplace le contenu ; SendKeys ajoutait à la fin |
SendKeys(Keys.Enter) | PressAsync("Enter") | |
Text | InnerTextAsync() | texte visible, comme Selenium |
Displayed, Enabled, Selected | IsVisibleAsync(), IsEnabledAsync(), IsCheckedAsync() | ces lectures n'attendent pas |
new SelectElement(e).SelectByText(t) | SelectOptionAsync(new SelectOptionValue { Label = t }) | |
new Actions(driver).MoveToElement(e) | HoverAsync() | |
DoubleClick, ContextClick, DragAndDrop | DblClickAsync(), ClickAsync(new() { Button = MouseButton.Right }), DragToAsync() | |
Navigate().GoToUrl(u) | Page.GotoAsync(u) | |
Title, PageSource | Page.TitleAsync(), Page.ContentAsync() | |
ExecuteScript("return …") | Page.EvaluateAsync<T>("…") | le type de retour est à préciser |
GetScreenshot().SaveAsFile(f) | Page.ScreenshotAsync(new() { Path = f }) |
Si un test saisit une valeur en plusieurs fois et compte sur l'ajout, utilisez PressSequentiallyAsync, qui tape caractère par caractère.
Attentes : supprimer plus que convertir
Une suite Selenium contient souvent trois couches d'attente : l'attente implicite, des WebDriverWait avec ExpectedConditions, et des Thread.Sleep. Avec Playwright :
ElementIsVisible,ElementToBeClickable: l'action elle-même attend. Si vous avez besoin d'attendre sans agir,Locator(sel).WaitForAsync(new() { State = WaitForSelectorState.Visible }).InvisibilityOfElementLocated:WaitForAsyncavec l'étatHiddenouDetached.UrlContains,TitleIs:Page.WaitForURLAsync(...), ou mieux une assertion web.Until(d => …)avec une condition libre :Page.WaitForFunctionAsyncsi la condition se lit dans la page, sinon une boucle de sondage avec le même délai.Thread.Sleep: à remplacer par une attente sur un état précis. UnTask.Delayest une traduction littérale, pas une solution.- Attente implicite : elle n'existe pas. Attention aux endroits où Selenium l'appliquait et où Playwright n'attend pas : compter des éléments, lire une liste, tester une visibilité.
Les assertions web remplacent avantageusement les attentes suivies d'un Assert : elles réessaient jusqu'au délai (5 secondes par défaut selon la documentation).
// Selenium
Assert.That(driver.Title, Is.EqualTo("Accueil"));
Assert.AreEqual("3", panier.Text);
// Playwright
await Expect(Page).ToHaveTitleAsync("Accueil");
await Expect(panier).ToHaveTextAsync("3");
Cadres, dialogues, fenêtres et fichiers
Cadres. Selenium change de contexte avec SwitchTo().Frame(...) puis SwitchTo().DefaultContent(). Playwright n'a pas d'état global : on localise à travers le cadre.
var cadre = Page.FrameLocator("#paiement");
await cadre.Locator("#numero").FillAsync("4111");
Le piège : une méthode utilitaire appelée après un SwitchTo().Frame travaillait implicitement dans le cadre. Il faut désormais lui passer le IFrameLocator.
Alertes et confirmations. Playwright ferme les dialogues automatiquement si aucun gestionnaire n'est posé. Pour accepter, posez le gestionnaire avant l'action qui ouvre le dialogue, et traitez-le dans le gestionnaire, sinon l'action reste bloquée :
Page.Dialog += async (_, dialog) => await dialog.AcceptAsync();
await Page.GetByRole(AriaRole.Button, new() { Name = "Supprimer" }).ClickAsync();
Fenêtres et onglets. WindowHandles et SwitchTo().Window(h) n'ont pas d'équivalent direct. On récupère la nouvelle page au moment où l'action l'ouvre :
var onglet = await Context.RunAndWaitForPageAsync(async () =>
{
await Page.GetByText("Ouvrir le rapport").ClickAsync();
});
await Expect(onglet).ToHaveTitleAsync("Rapport");
Pour une fenêtre surgissante ouverte par la page, Page.RunAndWaitForPopupAsync suit le même modèle.
Envoi de fichier. SendKeys(chemin) sur un input type="file" devient SetInputFilesAsync(chemin) ; un tableau envoie plusieurs fichiers, un tableau vide vide la sélection. Si le sélecteur de fichier s'ouvre par un clic sur un autre élément, utilisez Page.RunAndWaitForFileChooserAsync puis SetFilesAsync.
Le projet de test
- Remplacez les paquets
Selenium.*et les pilotes parMicrosoft.Playwright.NUnit,Microsoft.Playwright.MSTestouMicrosoft.Playwright.Xunit. - Faites hériter les classes de test de
PageTest: chaque test reçoit une page neuve dans son propre contexte de navigateur.ContextTestsert aux scénarios à plusieurs onglets. La création du pilote,Quit()etDispose()disparaissent. - Le navigateur et le mode sans interface se règlent dans un fichier
.runsettings(BrowserName,LaunchOptions), ou en ligne de commande avecdotnet test -- Playwright.BrowserName=firefox. - Les navigateurs ne s'installent pas avec le paquet NuGet : après un
dotnet build, lancezpwsh bin/Debug/net8.0/playwright.ps1 install(adapteznet8.0à votre cible), y compris sur les agents d'intégration continue. - Une grille Selenium (
RemoteWebDriver) se remplace parBrowserType.ConnectAsyncvers le point d'accès Playwright de votre fournisseur, s'il en propose un.
Plan de migration par étapes
- Mesurer. Comptez les tests, les page objects, les usages de
SwitchTo().Window,ExecuteAsyncScript,RemoteWebDriveret les cadres traversés par des utilitaires : ce sont eux qui coûtent du temps. - Faire cohabiter. Ajoutez Playwright au projet sans retirer Selenium, pour que tout compile pendant la transition.
- Migrer un pilote de 10 à 20 tests représentatifs, dont au moins un avec cadre, dialogue ou fenêtre. Mesurez le temps passé : c'est votre base d'estimation.
- Convertir par page object, avec ses tests, en propageant
async/await. Ne mélangez pas conversion et refonte des sélecteurs. - Nettoyer les attentes : retirer les
Sleep, transformer les attentes suivies d'assertions en assertions web. - Brancher l'intégration continue : installation des navigateurs,
.runsettings, rapports. - Retirer Selenium quand plus aucun fichier ne le référence.
Automatiser la conversion
Bascule fait l'étape 1 gratuitement : son diagnostic lit une solution C# avec Roslyn, sans la compiler ni l'exécuter, et classe chaque usage de Selenium en automatique, à relire ou à reprendre, avec le fichier et la ligne. L'édition Pro réécrit la suite en Playwright .NET selon les règles décrites plus haut, convertit le .csproj, recompile le résultat et signale chaque erreur apparue. Elle ne traite que le C# (NUnit, MSTest, xUnit 2), ne lance pas vos tests et ne convertit pas les fenêtres par identifiant, ExecuteAsyncScript, la grille distante ni Appium : ces points restent à écrire par vous. Une migration au forfait, à commencer par un pilote, est aussi proposée.
Sources
- Playwright for .NET, Auto-waiting
- Playwright for .NET, Locators
- Playwright for .NET, Actions
- Playwright for .NET, Frames
- Playwright for .NET, Dialogs
- Playwright for .NET, Pages
- Playwright for .NET, Assertions
- Playwright for .NET, Test runners
- Playwright for .NET, Installation
- Bascule, règles de conversion Selenium vers Playwright (docs/regles.md)
- Documentation relue le 2026-10-27