Logo Selenium

Selenium

Le framework d'automatisation de navigateurs web qui permet de contrôler différents navigateurs pour tester des applications web de manière cross-browser et automatisée.

Pour les non-initiés

Qu'est-ce que Selenium ?

Imaginez que vous ayez besoin de vérifier qu'un site web fonctionne correctement sur différents navigateurs comme Chrome, Firefox ou Safari. Au lieu de tester manuellement chaque fonctionnalité sur chaque navigateur (ce qui prendrait un temps considérable), Selenium vous permet d'automatiser ce processus.

Selenium est comme un pilote automatique pour navigateurs web : il peut contrôler votre navigateur pour cliquer sur des boutons, remplir des formulaires, naviguer entre les pages et vérifier que tout s'affiche correctement - le tout sans intervention humaine et beaucoup plus rapidement qu'un testeur manuel.

Ce que Selenium apporte à votre projet

Tests cross-browser

La possibilité de vérifier automatiquement que votre site fonctionne correctement sur différents navigateurs (Chrome, Firefox, Safari, Edge) sans avoir à tester manuellement chacun d'eux.

Gain de temps et d'argent

Une réduction drastique du temps consacré aux tests répétitifs, permettant à vos équipes de se concentrer sur des tâches à plus forte valeur ajoutée tout en maintenant une qualité élevée.

Régression automatisée

La capacité à vérifier rapidement qu'une nouvelle modification n'a pas cassé des fonctionnalités existantes, donnant l'assurance nécessaire pour déployer fréquemment de nouvelles versions.

Flexibilité multi-langages

Des outils utilisables avec différents langages de programmation (Java, JavaScript, Python, C#...), s'adaptant ainsi aux compétences et préférences de votre équipe.

En résumé, Selenium est un outil puissant qui automatise les tests web, assurant ainsi que votre site fonctionne correctement sur différents navigateurs, tout en réduisant le temps et les coûts associés aux tests manuels. Il contribue à améliorer la qualité des applications web et permet des cycles de livraison plus rapides et plus fiables.

Pour les développeurs

Fonctionnement technique

Selenium est un ensemble d'outils d'automatisation de navigateurs web qui permet de contrôler différents navigateurs de manière programmatique. Il est principalement utilisé pour les tests d'applications web, mais peut également servir à l'automatisation de tâches répétitives sur le web.

Composants de Selenium

L'écosystème Selenium comprend plusieurs composants complémentaires :

  • Selenium WebDriver - L'API principale qui permet de contrôler les navigateurs. Elle communique directement avec le navigateur en utilisant son pilote natif.
  • Selenium Grid - Permet l'exécution parallèle de tests sur différentes combinaisons de navigateurs et de systèmes d'exploitation.
  • Selenium IDE - Extension de navigateur qui permet d'enregistrer et de rejouer des interactions avec le navigateur (pratique pour les non-développeurs).

Dans cette page, nous nous concentrerons principalement sur Selenium WebDriver, qui est le composant le plus utilisé pour les tests automatisés.

Exemples pratiques avec Node.js

Test d'authentification de base

Voici un exemple simple de test Selenium avec Node.js qui vérifie le processus de connexion d'un utilisateur :

Test d'authentification avec Selenium WebDriver
// test/login.test.js const { Builder, By, Key, until } = require('selenium-webdriver'); const assert = require('assert'); describe('Authentification', function() { // Augmenter le timeout pour donner suffisamment de temps aux tests Selenium this.timeout(30000); let driver; // Avant chaque test, initialiser le driver et ouvrir le site beforeEach(async function() { driver = await new Builder().forBrowser('chrome').build(); await driver.manage().window().setRect({ width: 1280, height: 800 }); await driver.get('https://example.com'); }); // Après chaque test, fermer le navigateur afterEach(async function() { await driver.quit(); }); it('Permet à un utilisateur de se connecter avec des identifiants valides', async function() { // Cliquer sur le bouton de connexion await driver.findElement(By.linkText('Connexion')).click(); // Attendre que la page de connexion soit chargée await driver.wait(until.urlContains('/login'), 5000); // Remplir le formulaire de connexion await driver.findElement(By.name('email')).sendKeys('utilisateur@exemple.fr'); await driver.findElement(By.name('password')).sendKeys('motdepasse123', Key.RETURN); // Attendre que la redirection soit effectuée await driver.wait(until.urlContains('/tableau-de-bord'), 5000); // Vérifier que l'utilisateur est bien connecté const welcomeMessage = await driver.findElement(By.css('h1')).getText(); assert.strictEqual(welcomeMessage, 'Bienvenue, utilisateur@exemple.fr'); // Vérifier que le menu utilisateur est affiché const userMenu = await driver.findElement(By.className('user-menu')); assert(await userMenu.isDisplayed(), 'Le menu utilisateur devrait être visible'); }); it('Affiche un message d\'erreur avec des identifiants invalides', async function() { // Cliquer sur le bouton de connexion await driver.findElement(By.linkText('Connexion')).click(); // Remplir le formulaire avec des identifiants incorrects await driver.findElement(By.name('email')).sendKeys('utilisateur@exemple.fr'); await driver.findElement(By.name('password')).sendKeys('mauvais_mot_de_passe', Key.RETURN); // Attendre que le message d'erreur apparaisse const errorElement = await driver.wait( until.elementLocated(By.className('error-message')), 5000 ); // Vérifier le contenu du message d'erreur const errorText = await errorElement.getText(); assert.strictEqual(errorText, 'Identifiants invalides'); // Vérifier qu'on reste sur la page de login const currentUrl = await driver.getCurrentUrl(); assert(currentUrl.includes('/login'), 'L\'utilisateur devrait rester sur la page de login'); }); });

Pattern Page Object

Pour améliorer la maintenabilité des tests, le pattern "Page Object Model" est fortement recommandé avec Selenium :

Utilisation du pattern Page Object avec Selenium
// test/pageobjects/LoginPage.js class LoginPage { constructor(driver) { this.driver = driver; this.url = 'https://example.com/login'; // Définir les sélecteurs des éléments de la page this.emailInput = { locator: By.name('email') }; this.passwordInput = { locator: By.name('password') }; this.loginButton = { locator: By.css('button[type="submit"]') }; this.errorMessage = { locator: By.className('error-message') }; } // Ouvrir la page de connexion async open() { await this.driver.get(this.url); return this; } // Remplir le formulaire de connexion async login(email, password) { await this.driver.findElement(this.emailInput.locator).sendKeys(email); await this.driver.findElement(this.passwordInput.locator).sendKeys(password); await this.driver.findElement(this.loginButton.locator).click(); return this; } // Récupérer le message d'erreur s'il existe async getErrorMessage() { try { const errorElement = await this.driver.wait( until.elementLocated(this.errorMessage.locator), 5000 ); return await errorElement.getText(); } catch (error) { return null; } } } // test/pageobjects/DashboardPage.js class DashboardPage { constructor(driver) { this.driver = driver; this.url = 'https://example.com/tableau-de-bord'; // Définir les sélecteurs des éléments de la page this.welcomeMessage = { locator: By.css('h1') }; this.userMenu = { locator: By.className('user-menu') }; this.logoutButton = { locator: By.css('.user-menu .logout') }; } // Vérifier si on est bien sur la page de tableau de bord async isLoaded() { try { await this.driver.wait(until.urlContains('/tableau-de-bord'), 5000); return true; } catch (error) { return false; } } // Récupérer le message de bienvenue async getWelcomeMessage() { return await this.driver.findElement(this.welcomeMessage.locator).getText(); } // Se déconnecter async logout() { await this.driver.findElement(this.userMenu.locator).click(); await this.driver.findElement(this.logoutButton.locator).click(); return this; } } // test/login.pageobject.test.js const { Builder, By, Key, until } = require('selenium-webdriver'); const assert = require('assert'); const LoginPage = require('./pageobjects/LoginPage'); const DashboardPage = require('./pageobjects/DashboardPage'); describe('Authentification avec Page Objects', function() { this.timeout(30000); let driver; let loginPage; let dashboardPage; beforeEach(async function() { driver = await new Builder().forBrowser('chrome').build(); await driver.manage().window().setRect({ width: 1280, height: 800 }); // Initialiser les page objects loginPage = new LoginPage(driver); dashboardPage = new DashboardPage(driver); }); afterEach(async function() { await driver.quit(); }); it('Permet à un utilisateur de se connecter avec des identifiants valides', async function() { // Ouvrir la page de connexion et se connecter await loginPage.open(); await loginPage.login('utilisateur@exemple.fr', 'motdepasse123'); // Vérifier qu'on est bien sur le tableau de bord const isDashboardLoaded = await dashboardPage.isLoaded(); assert.strictEqual(isDashboardLoaded, true, 'Le tableau de bord devrait être chargé'); // Vérifier le message de bienvenue const welcomeMessage = await dashboardPage.getWelcomeMessage(); assert.strictEqual(welcomeMessage, 'Bienvenue, utilisateur@exemple.fr'); }); it('Affiche un message d\'erreur avec des identifiants invalides', async function() { // Ouvrir la page de connexion et essayer de se connecter avec des identifiants invalides await loginPage.open(); await loginPage.login('utilisateur@exemple.fr', 'mauvais_mot_de_passe'); // Vérifier le message d'erreur const errorMessage = await loginPage.getErrorMessage(); assert.strictEqual(errorMessage, 'Identifiants invalides'); }); });

Tests cross-browser avec Selenium Grid

Selenium Grid permet d'exécuter des tests sur différents navigateurs en parallèle :

Tests cross-browser avec Selenium Grid
// test/grid.test.js const { Builder, By, Key, until } = require('selenium-webdriver'); const assert = require('assert'); // Fonction pour créer un driver pour un navigateur spécifique sur Selenium Grid function createDriver(browserName) { return new Builder() .usingServer('http://localhost:4444/wd/hub') // URL du Selenium Grid .withCapabilities({ browserName: browserName, 'goog:chromeOptions': browserName === 'chrome' ? { args: ['--headless', '--disable-gpu', '--window-size=1280,800'] } : undefined, 'moz:firefoxOptions': browserName === 'firefox' ? { args: ['-headless'] } : undefined }) .build(); } // Fonction de test réutilisable pour différents navigateurs async function runTest(browserName) { let driver; try { console.log(`Démarrage du test sur ${browserName}`); driver = await createDriver(browserName); // Ouvrir le site await driver.get('https://example.com'); // Exécuter des actions de test await driver.findElement(By.linkText('Connexion')).click(); // Remplir le formulaire await driver.findElement(By.name('email')).sendKeys('utilisateur@exemple.fr'); await driver.findElement(By.name('password')).sendKeys('motdepasse123', Key.RETURN); // Vérifier le résultat await driver.wait(until.urlContains('/tableau-de-bord'), 5000); const welcomeMessage = await driver.findElement(By.css('h1')).getText(); assert.strictEqual(welcomeMessage, 'Bienvenue, utilisateur@exemple.fr'); console.log(`Test réussi sur ${browserName}`); } catch (error) { console.error(`Erreur lors du test sur ${browserName}:`, error); throw error; } finally { if (driver) { await driver.quit(); } } } describe('Tests cross-browser avec Selenium Grid', function() { this.timeout(60000); // Timeout plus long pour les tests Grid it('Fonctionne sur Chrome', async function() { await runTest('chrome'); }); it('Fonctionne sur Firefox', async function() { await runTest('firefox'); }); it('Fonctionne sur Edge', async function() { await runTest('MicrosoftEdge'); }); }); // Pour exécuter ces tests, vous devez d'abord avoir un Selenium Grid en cours d'exécution // Installation et démarrage de Selenium Grid : // // 1. Télécharger le Selenium Server (Grid) jar : https://www.selenium.dev/downloads/ // 2. Démarrer le hub : java -jar selenium-server-4.x.x.jar hub // 3. Démarrer les nodes : java -jar selenium-server-4.x.x.jar node --hub http://localhost:4444

Stratégies d'attente

Un défi majeur avec Selenium est de gérer les attentes pour les éléments asynchrones. Voici différentes stratégies :

Stratégies d'attente dans Selenium
// test/waiting.test.js const { Builder, By, Key, until } = require('selenium-webdriver'); const assert = require('assert'); describe('Stratégies d\'attente dans Selenium', function() { this.timeout(30000); let driver; beforeEach(async function() { driver = await new Builder().forBrowser('chrome').build(); await driver.manage().window().setRect({ width: 1280, height: 800 }); }); afterEach(async function() { await driver.quit(); }); it('Utilise des attentes explicites pour les éléments', async function() { await driver.get('https://example.com/products'); // 1. Attendre qu'un élément soit présent dans le DOM const productListElement = await driver.wait( until.elementLocated(By.className('product-list')), 10000, 'La liste de produits n\'est pas apparue dans le délai imparti' ); // 2. Attendre qu'un élément soit visible await driver.wait( until.elementIsVisible(productListElement), 5000, 'La liste de produits est dans le DOM mais n\'est pas visible' ); // 3. Attendre qu'un élément soit cliquable const addToCartButton = await driver.wait( until.elementLocated(By.css('.product:first-child .add-to-cart')), 5000 ); await driver.wait( until.elementIsEnabled(addToCartButton), 5000, 'Le bouton Ajouter au panier n\'est pas activé' ); // Cliquer sur le bouton une fois qu'il est prêt await addToCartButton.click(); // 4. Attendre qu'un texte soit présent dans un élément const cartCountElement = await driver.findElement(By.className('cart-count')); await driver.wait( until.elementTextIs(cartCountElement, '1'), 5000, 'Le compteur du panier n\'a pas été mis à jour' ); // 5. Attendre une condition personnalisée await driver.wait( async function() { const notificationElement = await driver.findElement(By.className('notification')); const isDisplayed = await notificationElement.isDisplayed(); const text = await notificationElement.getText(); return isDisplayed && text.includes('Produit ajouté au panier'); }, 5000, 'La notification de produit ajouté n\'est pas apparue' ); }); it('Gère les attentes pour les changements d\'URL et de titre', async function() { await driver.get('https://example.com'); // Cliquer sur un lien qui va charger une nouvelle page await driver.findElement(By.linkText('Produits')).click(); // Attendre que l'URL change await driver.wait( until.urlContains('/products'), 5000, 'L\'URL n\'a pas changé vers la page des produits' ); // Attendre que le titre de la page change await driver.wait( until.titleContains('Catalogue de produits'), 5000, 'Le titre de la page n\'a pas été mis à jour' ); // Vérifier que la page s'est bien chargée const heading = await driver.findElement(By.css('h1')).getText(); assert.strictEqual(heading, 'Catalogue de produits'); }); it('Utilise des timeouts implicites comme filet de sécurité', async function() { // Définir un timeout implicite (à utiliser avec parcimonie) await driver.manage().setTimeouts({ implicit: 5000 }); await driver.get('https://example.com/slow-loading-page'); // L'élément n'est pas immédiatement disponible, mais le timeout implicite attend // automatiquement jusqu'à 5 secondes avant d'échouer const slowElement = await driver.findElement(By.id('slow-loading-element')); // Vérifier que l'élément est bien présent assert(await slowElement.isDisplayed(), 'L\'élément à chargement lent devrait être visible'); // Remettre le timeout implicite à une valeur basse pour le reste des tests await driver.manage().setTimeouts({ implicit: 0 }); }); });

Installation et configuration

Pour utiliser Selenium avec Node.js, vous devez d'abord installer les packages nécessaires :

npm install selenium-webdriver

Vous aurez également besoin des pilotes spécifiques aux navigateurs que vous souhaitez tester :

# Pour Chrome npm install chromedriver # Pour Firefox npm install geckodriver # Pour Edge npm install msedgedriver

Alternativement, vous pouvez utiliser le package webdriver-manager pour gérer automatiquement les pilotes avec Icône Node.jsNode.js :

npm install -g webdriver-manager webdriver-manager update webdriver-manager start # Démarre un serveur Selenium avec les pilotes installés

Bonnes pratiques

  • Utiliser le pattern Page Object - Séparer la logique de test de la représentation de la page pour améliorer la maintenabilité
  • Privilégier les attentes explicites - Plutôt que d'utiliser des délais fixes (sleep) ou des attentes implicites
  • Utiliser des sélecteurs stables - Idéalement des attributs data-* dédiés aux tests plutôt que des classes CSS qui peuvent changer
  • Tests atomiques et indépendants - Chaque test doit pouvoir s'exécuter seul et ne pas dépendre des autres tests
  • Captures d'écran automatiques - En cas d'échec d'un test pour faciliter le débogage
  • Parallélisation des tests - Utiliser Selenium Grid pour exécuter des tests sur plusieurs navigateurs simultanément
  • Structure des tests - Suivre le pattern AAA (Arrange, Act, Assert) pour une meilleure lisibilité

Avantages et limitations

Avantages de Selenium

  • Support multiplateforme - Fonctionne sur Windows, Mac, Linux
  • Support multi-navigateurs - Chrome, Firefox, Safari, Edge, etc.
  • Support multi-langages - Java, Icône JavaScriptJavaScript, Python, C#, Ruby, etc.
  • Flexibilité - Peut être utilisé pour toute application web, quelle que soit la technologie
  • Communauté importante - Ressources, documentation et support abondants
  • Open source - Gratuit et activement maintenu

Limitations

  • Tests plus lents - Comparé à des frameworks de test plus modernes comme Icône CypressCypress
  • Configuration complexe - Notamment pour Selenium Grid et l'exécution cross-browser
  • Tests instables - Peut souffrir de "flakiness" (tests qui échouent de manière intermittente)
  • Débogage difficile - Moins d'outils de débogage intégrés que des alternatives plus récentes
  • Maintenance des pilotes - Nécessité de mettre à jour les pilotes lors des mises à jour des navigateurs

Malgré l'émergence d'alternatives plus modernes, Selenium reste un outil extrêmement populaire et puissant pour l'automatisation des tests web, notamment grâce à sa flexibilité, sa compatibilité avec de nombreux environnements et sa capacité à tester réellement sur différents navigateurs.

Applications concrètes

Cas d'usage

Tests de compatibilité cross-browser

Vérification automatisée du fonctionnement de sites web sur différents navigateurs (Chrome, Firefox, Safari, Edge) et différentes versions, particulièrement crucial pour les applications avec une large base d'utilisateurs.

Tests de régression automatisés

Exécution de suites de tests complètes avant chaque déploiement pour s'assurer que les nouvelles fonctionnalités n'ont pas cassé les fonctionnalités existantes, permettant une intégration continue fiable.

Applications d'entreprise complexes

Test de workflows métier complexes dans les applications d'entreprise, comme les ERP, les CRM ou les systèmes bancaires, où la fiabilité est critique et les parcours utilisateurs sont nombreux.

Intégration avec outils CI/CD

Exécution automatique des tests Selenium dans des pipelines d'intégration continue (Jenkins, Icône GitHub ActionsGitHub Actions, GitLab CI), permettant de détecter rapidement les régressions et d'assurer la qualité continue.

Tests sur différents environnements

Exécution de tests sur différentes combinaisons d'OS et de navigateurs en utilisant Selenium Grid, assurant une expérience utilisateur cohérente sur tous les environnements supportés.

Tests de non-régression planifiés

Exécution programmée de suites de tests sur des environnements de production ou de préproduction pour s'assurer que les fonctionnalités critiques restent opérationnelles au fil du temps.

Intégration avec des services de test cloud

Selenium peut être intégré avec des services de test cloud comme BrowserStack, Sauce Labs ou LambdaTest pour exécuter des tests sur une multitude de combinaisons navigateur/OS sans avoir à maintenir une infrastructure locale :

Exemple d'utilisation de Selenium avec BrowserStack
const { Builder } = require('selenium-webdriver'); async function runTestOnBrowserStack() { const capabilities = { 'browserName': 'Chrome', 'browser_version': 'latest', 'os': 'Windows', 'os_version': '10', 'name': 'Mon test Selenium', 'build': 'Build 1.0', 'browserstack.user': process.env.BROWSERSTACK_USERNAME, 'browserstack.key': process.env.BROWSERSTACK_ACCESS_KEY }; const driver = await new Builder() .usingServer('https://hub-cloud.browserstack.com/wd/hub') .withCapabilities(capabilities) .build(); try { await driver.get('https://example.com'); // Exécuter les actions de test... } finally { await driver.quit(); } }