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.
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.
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/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 :
// 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 :
// 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 :
// 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 Node.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,
JavaScript, 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
Cypress
- 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.
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, GitHub 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 :
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();
}
}