Formation Test Web – Fondations

Devenez autonome : comprendre le Web, HTML, CSS, navigateur et DOM.

📚 Fondations du Web

Avant de pouvoir tester et automatiser des applications Web, il est essentiel de comprendre les fondations : comment fonctionne le Web, comment on structure une page en HTML, la mettre en forme avec CSS, comment le navigateur l'interprète via le DOM, et quels sont les enjeux de performance et de sécurité.

🌍 1. Architecture du Web

Le Web repose sur un modèle client-serveur. Votre navigateur est le client, il envoie des requêtes HTTP/HTTPS à un serveur, qui renvoie des réponses.

  • DNS : traduit un nom de domaine (ex. google.com) en adresse IP.
  • HTTP : protocole de communication (méthodes GET, POST, PUT, DELETE).
  • HTTPS : HTTP sécurisé via TLS/SSL.
  • Cookies & Sessions : permettent l’authentification et le suivi des utilisateurs.
  • REST : style d’architecture basé sur les ressources (API RESTful).
⚡ Exemple : GET https://api.github.com/users/octocat → renvoie un objet JSON représentant l’utilisateur.

🧱 2. HTML (HyperText Markup Language)

Le HTML est le langage de structure du Web. Il définit les balises qui composent une page : titres, paragraphes, liens, images, formulaires…

Structure de base

<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8">
    <title>Ma première page</title>
  </head>
  <body>
    <header><h1>Bienvenue</h1></header>
    <main>
      <p>Ceci est un paragraphe.</p>
    </main>
    <footer>© 2025 Moi</footer>
  </body>
</html>

Éléments importants

  • Titres : <h1> à <h6>
  • Texte : <p>, <strong>, <em>
  • Listes : <ul>, <ol>, <li>
  • Liens : <a href="..." target="_blank">
  • Images : <img src="photo.jpg" alt="Description">
  • Tableaux : <table>, <tr>, <td>
  • Formulaires : <form>, <input>, <button>
🎯 Accessibilité : toujours utiliser alt pour les images, label pour les champs de formulaire, et aria-* pour améliorer l’expérience des lecteurs d’écran.

🎨 3. CSS (Cascading Style Sheets)

Le CSS est le langage de présentation : couleurs, polices, layout, animations.

Sélecteurs et cascade

p { color: blue; }
#id { font-size: 20px; }
.article > h2 { font-weight: bold; }
a:hover { text-decoration: underline; }

Box Model

  • content → zone du texte ou image
  • padding → espace intérieur
  • border → bordure
  • margin → espace extérieur

Layout moderne

/* Flexbox */
.container {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Grid */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

Animations

@keyframes blink {
  0% { opacity: 1; }
  50% { opacity: 0; }
  100% { opacity: 1; }
}
.blinking { animation: blink 1s infinite; }
💡 Astuce : utilisez variables CSS pour centraliser vos couleurs et polices : :root { --main-color: #0070f3; }

🖥️ 4. DOM & fonctionnement du navigateur

Le DOM (Document Object Model) est la représentation interne d’une page HTML sous forme d’arbre. Le navigateur construit le DOM, le CSSOM (arbre des styles), les combine en Render Tree, puis effectue :

  1. Layout → calcul des positions et tailles
  2. Paint → dessin des pixels
  3. Composite → affichage final à l’écran
🚀 L’event loop gère l’exécution : Call stack → Web APIs → Callback queue → Event loop → Execution.

Exemple JavaScript manipulant le DOM

document.getElementById("btn").addEventListener("click", () => {
  alert("Bouton cliqué !");
});

⚡ 5. Performance & sécurité

  • Performance : minifier CSS/JS, compresser (GZIP, Brotli), lazy loading images, critical CSS.
  • Accessibilité : utiliser des contrastes suffisants, tabulation logique, aria-*.
  • Sécurité : protéger contre XSS (ne jamais injecter du HTML brut), CSRF (tokens), utiliser HTTPS, définir des en-têtes CSP.

🛠️ 6. Exercices pratiques

  1. Créer une page portfolio avec un header, une section principale et un footer.
  2. Ajouter une grille CSS pour afficher 6 projets avec images et titres.
  3. Rendre la page responsive avec @media.
  4. Ajouter un formulaire de contact (nom, email, message) avec validation HTML5.
  5. Optimiser les performances (lazy loading images, minification).
🎯 À la fin de cette section, vous devez être capable de coder une page web complète responsive, accessible et performante, en comprenant tout le cycle de rendu du navigateur.

⚙️ HTML & CSS – Encyclopédie complète

Cette section couvre en détail les deux piliers incontournables du Web : HTML (structure et contenu) et CSS (présentation et style). En maîtrisant ces deux langages, vous serez capable de construire des pages web robustes, sémantiques, accessibles et responsive.

🧱 1. HTML – Le squelette du Web

Le HTML (HyperText Markup Language) décrit la structure d’une page. Chaque élément est défini par une balise entourée de <>.

1.1. Structure minimale

<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8">
    <title>Titre de la page</title>
  </head>
  <body>
    <h1>Mon premier titre</h1>
    <p>Un paragraphe de texte.</p>
  </body>
</html>

1.2. Balises principales

  • Titres : <h1> à <h6>
  • Texte : <p>, <strong>, <em>, <blockquote>
  • Listes : <ul>, <ol>, <li>
  • Liens : <a href="url">
  • Images : <img src="img.jpg" alt="description">
  • Tableaux : <table>, <tr>, <th>, <td>
  • Multimédia : <audio>, <video>

1.3. Balises sémantiques

Depuis HTML5, certaines balises décrivent le rôle du contenu :

  • <header> : en-tête de page ou de section.
  • <nav> : zone de navigation.
  • <main> : contenu principal.
  • <article> : contenu autonome (article, post de blog).
  • <section> : regroupement thématique.
  • <aside> : contenu secondaire (sidebar).
  • <footer> : pied de page.

1.4. Formulaires

<form action="/login" method="post">
  <label for="email">Email</label>
  <input type="email" id="email" name="email" required>

  <label for="password">Mot de passe</label>
  <input type="password" id="password" name="password" required>

  <button type="submit">Se connecter</button>
</form>
🎯 Accessibilité : chaque <input> doit avoir un <label> associé.

1.5. Tableaux

<table>
  <thead>
    <tr><th>Nom</th><th>Âge</th></tr>
  </thead>
  <tbody>
    <tr><td>Alice</td><td>25</td></tr>
    <tr><td>Bob</td><td>30</td></tr>
  </tbody>
</table>

1.6. Accessibilité

  • Ajouter alt aux images.
  • Utiliser des balises sémantiques plutôt que des <div> génériques.
  • Employer les attributs aria-* pour décrire les interactions.

🎨 2. CSS – La peinture du Web

Le CSS (Cascading Style Sheets) gère la présentation des éléments HTML : couleurs, polices, marges, disposition, animations.

2.1. Cascade, héritage et spécificité

  • Héritage : certaines propriétés (police, couleur) se transmettent aux enfants.
  • Cascade : la dernière règle appliquée l’emporte si elle a le même poids.
  • Spécificité : un #id est plus fort qu’une .classe.

2.2. Sélecteurs

/* Type */
p { color: black; }

/* Classe */
.highlight { background: yellow; }

/* ID */
#unique { border: 1px solid red; }

/* Combinés */
nav ul li a:hover { color: blue; }

2.3. Box Model

Chaque élément est une boîte composée de :

  • Content → contenu texte/image
  • Padding → marge intérieure
  • Border → bordure
  • Margin → marge extérieure

2.4. Positionnement

  • static (par défaut)
  • relative (décalage depuis la position normale)
  • absolute (relatif au parent positionné)
  • fixed (par rapport à la fenêtre)
  • sticky (fixe après un certain scroll)

2.5. Flexbox

.container {
  display: flex;
  justify-content: space-around;
  align-items: center;
}

2.6. CSS Grid

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

2.7. Responsive design

@media (max-width: 768px) {
  body { font-size: 14px; }
  .grid { grid-template-columns: 1fr; }
}

2.8. Variables CSS

:root {
  --main-color: #0070f3;
}
h1 { color: var(--main-color); }

2.9. Animations

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
.fade { animation: fadeIn 2s ease-in-out; }

✅ 3. Bonnes pratiques

  • Utiliser des balises sémantiques en HTML.
  • Organiser le CSS (BEM, ITCSS, ou autre convention).
  • Privilégier classes plutôt que IDs pour le style.
  • Éviter le inline CSS → privilégier les feuilles externes.
  • Tester le rendu sur plusieurs navigateurs et tailles d’écran.

🛠️ 4. Exercices pratiques

Dans cette section, nous allons construire pas à pas une page personnelle complète en HTML et CSS. Chaque étape est expliquée, suivie d’un exemple de code à copier-coller et tester.

Étape 1 – Créer une structure HTML de base

On commence par un squelette simple avec un <header>, une section "À propos", une section "Compétences" et un formulaire de contact.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Ma Page Personnelle</title>
</head>
<body>
  <header>
    <h1>Bienvenue sur ma page</h1>
    <p>Développeur passionné par le web</p>
  </header>

  <section id="about">
    <h2>À propos</h2>
    <p>Je suis Julien, architecte QA, passionné par l’automatisation et la qualité logicielle.</p>
  </section>

  <section id="skills">
    <h2>Compétences</h2>
    <ul>
      <li>Automatisation de tests</li>
      <li>DevOps et CI/CD</li>
      <li>Architecture logicielle</li>
    </ul>
  </section>

  <section id="contact">
    <h2>Contact</h2>
    <form>
      <label for="name">Nom :</label>
      <input type="text" id="name" name="name" required>

      <label for="email">Email :</label>
      <input type="email" id="email" name="email" required>

      <label for="message">Message :</label>
      <textarea id="message" name="message" rows="4"></textarea>

      <button type="submit">Envoyer</button>
    </form>
  </section>
</body>
</html>
💡 Ici, on a toute la structure HTML sans style : juste le squelette logique.

Étape 2 – Ajouter du style CSS de base

On stylise les grandes zones pour leur donner de la personnalité.

body {
  font-family: Arial, sans-serif;
  margin: 0;
  line-height: 1.6;
  color: #333;
}

header {
  background: linear-gradient(135deg, #004a7c, #005b96);
  color: white;
  text-align: center;
  padding: 2rem;
}

section {
  padding: 2rem;
}

h2 {
  color: #004a7c;
}

form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

input, textarea, button {
  padding: 0.8rem;
  border: 1px solid #ccc;
  border-radius: 6px;
}

button {
  background: #005b96;
  color: white;
  border: none;
  cursor: pointer;
}
button:hover {
  background: #004a7c;
}
💡 On a mis un header en dégradé bleu, des titres bleus, un formulaire lisible et arrondi.

Étape 3 – Layout en 2 colonnes (responsive)

On fait une mise en page moderne avec CSS Grid.

@media (min-width: 768px) {
  body {
    display: grid;
    grid-template-columns: 1fr 2fr;
    grid-template-areas:
      "header header"
      "about skills"
      "contact contact";
  }
  header { grid-area: header; }
  #about { grid-area: about; }
  #skills { grid-area: skills; }
  #contact { grid-area: contact; }
}
💡 Sur mobile → tout en colonne. Sur tablette/PC → “À propos” et “Compétences” côte à côte.

Étape 4 – Animation au survol

On ajoute une petite interaction sympa pour les boutons.

button {
  transition: transform 0.2s ease;
}
button:hover {
  transform: scale(1.05);
}
💡 Le bouton grossit légèrement → feedback visuel agréable pour l’utilisateur.

Étape 5 – Accessibilité

  • Chaque image doit avoir un alt.
  • Les <label> sont reliés aux inputs par for.
  • Les couleurs respectent le contraste WCAG (bleu foncé + texte blanc).
🎯 À la fin de cet exercice, vous êtes capables de coder une page web complète, responsive, accessible, performante et moderne.

📝 Formation JavaScript Complète - De Zéro à Héros

Cette formation vous apprendra JavaScript de manière progressive et approfondie. Chaque concept est expliqué avec le pourquoi, le comment et des exemples concrets que vous pouvez tester immédiatement.

1. Qu'est-ce que JavaScript et pourquoi l'apprendre ?

Histoire et contexte

JavaScript a été créé en 1995 par Brendan Eich en seulement 10 jours pour le navigateur Netscape. L'objectif était simple : permettre aux pages web d'être interactives au lieu d'être statiques.

Pourquoi JavaScript est-il incontournable aujourd'hui ?

  • Côté client : Seul langage natif du navigateur pour l'interactivité
  • Côté serveur : Node.js permet d'utiliser JS partout
  • Applications mobiles : React Native, Ionic
  • Applications desktop : Electron (VS Code, Discord)
  • IoT et embarqué : microcontrôleurs supportent JS
💡 Conseil pratique : Ouvrez la console de votre navigateur (F12 → Console) pour tester tous les exemples de cette formation en temps réel.

Premier contact

Voici votre premier programme JavaScript. Copiez cette ligne dans la console de votre navigateur :

alert("Bonjour, je commence à apprendre JavaScript !");

Que s'est-il passé ? JavaScript a exécuté une fonction qui affiche une popup. C'est votre première interaction avec le langage !

2. Variables et types de données

Pourquoi a-t-on besoin de variables ?

Une variable est comme une boîte étiquetée où on stocke des informations. Sans variables, impossible de mémoriser des données ou de créer des programmes utiles.

Les trois façons de déclarer une variable

var ancien = "à éviter en 2025";     // Ancienne syntaxe, problématique
let moderne = "peut changer";         // Variable moderne, modifiable
const fixe = "ne peut pas changer";   // Constante, valeur figée

Pourquoi éviter var ?

// Problème avec var : portée (scope) confuse
function exempleProblematique() {
  for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // Affiche 3, 3, 3 au lieu de 0, 1, 2
  }
}

// Solution avec let : portée correcte
function exempleBon() {
  for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // Affiche 0, 1, 2
  }
}

Règle pratique : Utilisez toujours const par défaut. Si vous devez modifier la variable, utilisez let.

Types de données primitifs

JavaScript reconnaît automatiquement le type de vos données. Voici les types principaux :

// String (chaîne de caractères)
let nom = "Julien";
let message = 'Bonjour';           // Guillemets simples ou doubles
let template = `Salut ${nom} !`;   // Template literals avec backticks

// Number (nombre)
let age = 35;                      // Entier
let prix = 19.99;                  // Décimal
let infini = Infinity;             // Valeur spéciale
let pasUnNombre = NaN;             // "Not a Number"

// Boolean (vrai/faux)
let estConnecte = true;
let estAdmin = false;

// Null et undefined
let vide = null;                   // Volontairement vide
let x;                            // Non défini = undefined
console.log(x);                   // Affiche: undefined

Types de données complexes

// Array (tableau) - liste ordonnée
let competences = ["JavaScript", "HTML", "CSS"];
let nombres = [1, 2, 3, 4, 5];
let mixte = ["texte", 42, true];   // Peut mélanger les types

// Object (objet) - collection de propriétés
let personne = {
  prenom: "Julien",
  age: 35,
  estQA: true,
  competences: ["JS", "QA", "DevOps"]
};

// Accès aux propriétés
console.log(personne.prenom);      // "Julien"
console.log(personne["age"]);      // 35

Vérifier le type d'une variable

console.log(typeof "Hello");       // "string"
console.log(typeof 42);            // "number"
console.log(typeof true);          // "boolean"
console.log(typeof undefined);     // "undefined"
console.log(typeof null);          // "object" (c'est un bug historique !)
console.log(typeof [1,2,3]);       // "object"
console.log(typeof {nom: "Test"}); // "object"

3. Opérateurs : calculer et comparer

Opérateurs arithmétiques

let a = 10;
let b = 3;

console.log(a + b);    // 13 (addition)
console.log(a - b);    // 7  (soustraction)
console.log(a * b);    // 30 (multiplication)
console.log(a / b);    // 3.333... (division)
console.log(a % b);    // 1  (reste de la division = modulo)
console.log(a ** b);   // 1000 (puissance: 10^3)

// Incrémentation/décrémentation
let compteur = 5;
compteur++;            // compteur = 6 (ajoute 1 après)
++compteur;            // compteur = 7 (ajoute 1 avant)
compteur--;            // compteur = 6 (retire 1 après)

Opérateurs de comparaison

// Égalité stricte vs égalité faible
console.log(5 == "5");     // true  (conversion automatique)
console.log(5 === "5");    // false (types différents)
console.log(5 != "6");     // true
console.log(5 !== "5");    // true  (types différents)

// Comparaisons
console.log(10 > 5);       // true
console.log(10 >= 10);     // true
console.log(5 < 3);        // false
console.log(5 <= 5);       // true

Règle d'or : Utilisez toujours === et !== pour éviter les conversions automatiques surprenantes.

Opérateurs logiques

let estConnecte = true;
let estAdmin = false;

// ET logique (&&) - toutes les conditions doivent être vraies
console.log(estConnecte && estAdmin);        // false

// OU logique (||) - au moins une condition doit être vraie  
console.log(estConnecte || estAdmin);        // true

// NON logique (!) - inverse la valeur
console.log(!estConnecte);                   // false
console.log(!estAdmin);                      // true

// Utilisation pratique avec des valeurs par défaut
let nom = undefined;
let nomAffiche = nom || "Utilisateur anonyme";  // "Utilisateur anonyme"

4. Structures de contrôle : prendre des décisions

Conditions avec if/else

Les conditions permettent à votre programme de prendre des décisions basées sur les données.

let age = 20;

if (age >= 18) {
  console.log("Vous êtes majeur");
} else {
  console.log("Vous êtes mineur");
}

// Conditions multiples
let note = 85;

if (note >= 90) {
  console.log("Excellente note !");
} else if (note >= 70) {
  console.log("Bonne note");
} else if (note >= 50) {
  console.log("Note passable");
} else {
  console.log("Note insuffisante");
}

Opérateur ternaire (condition compacte)

let age = 20;
let statut = age >= 18 ? "majeur" : "mineur";
console.log(statut); // "majeur"

// Équivalent en if/else plus verbeux :
let statut2;
if (age >= 18) {
  statut2 = "majeur";
} else {
  statut2 = "mineur";
}

Switch : choix multiples

Quand vous avez plusieurs valeurs possibles pour une même variable, switch est plus lisible que des if/else en cascade.

let jour = "lundi";

switch (jour) {
  case "lundi":
    console.log("Début de semaine");
    break;
  case "mardi":
  case "mercredi":
  case "jeudi":
    console.log("Milieu de semaine");
    break;
  case "vendredi":
    console.log("Bientôt le weekend !");
    break;
  case "samedi":
  case "dimanche":
    console.log("Weekend !");
    break;
  default:
    console.log("Jour non reconnu");
}

// IMPORTANT: n'oubliez pas les 'break' sinon ça continue !

5. Boucles : répéter des actions

Boucle for classique

Utilisée quand vous connaissez à l'avance le nombre d'itérations.

// Compter de 0 à 4
for (let i = 0; i < 5; i++) {
  console.log("Itération numéro : " + i);
}

// Parcourir un tableau avec les indices
let fruits = ["pomme", "banane", "orange"];
for (let i = 0; i < fruits.length; i++) {
  console.log(`Fruit ${i + 1}: ${fruits[i]}`);
}

Boucle for...of (pour les tableaux)

Plus simple et moderne pour parcourir les éléments d'un tableau.

let competences = ["JavaScript", "HTML", "CSS"];

for (let competence of competences) {
  console.log("Je maîtrise : " + competence);
}

// Plus lisible que :
for (let i = 0; i < competences.length; i++) {
  console.log("Je maîtrise : " + competences[i]);
}

Boucle for...in (pour les objets)

let personne = {
  nom: "Julien",
  age: 35,
  role: "QA Architect"
};

for (let propriete in personne) {
  console.log(`${propriete}: ${personne[propriete]}`);
}
// Affiche:
// nom: Julien
// age: 35
// role: QA Architect

Boucle while

Utilisée quand vous ne savez pas à l'avance combien d'itérations seront nécessaires.

let compteur = 0;
while (compteur < 3) {
  console.log("Compteur : " + compteur);
  compteur++; // IMPORTANT: n'oubliez pas d'incrémenter !
}

// Exemple pratique : attendre une condition
let nombreAleatoire;
let tentatives = 0;
while (nombreAleatoire !== 5) {
  nombreAleatoire = Math.floor(Math.random() * 10) + 1; // 1-10
  tentatives++;
  console.log(`Tentative ${tentatives}: ${nombreAleatoire}`);
}
console.log(`Trouvé 5 en ${tentatives} tentatives !`);

6. Fonctions : organiser et réutiliser le code

Pourquoi utiliser des fonctions ?

Les fonctions permettent de découper votre code en petits blocs réutilisables. Au lieu d'écrire le même code plusieurs fois, vous l'encapsulez dans une fonction.

Déclaration de fonction classique

function saluer(nom) {
  return "Bonjour " + nom + " !";
}

// Utilisation
let message = saluer("Julien");
console.log(message); // "Bonjour Julien !"

// Fonction avec plusieurs paramètres
function calculerAge(anneeNaissance, anneeActuelle) {
  return anneeActuelle - anneeNaissance;
}

let age = calculerAge(1988, 2025);
console.log(age); // 37

Fonctions fléchées (arrow functions)

Syntaxe plus moderne et concise, introduite en ES6 (2015).

// Fonction classique
function multiplier(a, b) {
  return a * b;
}

// Fonction fléchée équivalente
const multiplier2 = (a, b) => {
  return a * b;
};

// Version ultra-compacte (pour une seule expression)
const multiplier3 = (a, b) => a * b;

// Un seul paramètre : pas besoin de parenthèses
const doubler = x => x * 2;

// Aucun paramètre : parenthèses obligatoires
const direBonjour = () => "Bonjour !";

console.log(multiplier3(4, 5)); // 20
console.log(doubler(7)); // 14
console.log(direBonjour()); // "Bonjour !"

Paramètres par défaut

function creerProfil(nom, age = 25, role = "Utilisateur") {
  return {
    nom: nom,
    age: age,
    role: role
  };
}

console.log(creerProfil("Alice")); 
// { nom: "Alice", age: 25, role: "Utilisateur" }

console.log(creerProfil("Bob", 30, "Admin"));
// { nom: "Bob", age: 30, role: "Admin" }

Fonctions de rappel (callbacks)

Une fonction peut prendre une autre fonction en paramètre. C'est très courant en JavaScript.

function traiterDonnees(donnees, callback) {
  console.log("Traitement en cours...");
  let resultat = donnees.map(x => x * 2);
  callback(resultat);
}

function afficherResultat(resultat) {
  console.log("Résultat :", resultat);
}

traiterDonnees([1, 2, 3, 4], afficherResultat);
// Traitement en cours...
// Résultat : [2, 4, 6, 8]

// Version avec fonction anonyme
traiterDonnees([5, 6, 7], function(resultat) {
  console.log("Nouveau résultat :", resultat);
});

// Version avec fonction fléchée
traiterDonnees([8, 9, 10], (resultat) => {
  console.log("Résultat final :", resultat);
});

Closures : la magie des fonctions JavaScript

Une closure permet à une fonction d'accéder aux variables de sa fonction parente, même après que la fonction parente ait terminé son exécution.

function creerCompteur() {
  let count = 0; // Variable privée
  
  return function() {
    count++; // Accès à la variable de la fonction parente
    return count;
  };
}

let compteur1 = creerCompteur();
let compteur2 = creerCompteur();

console.log(compteur1()); // 1
console.log(compteur1()); // 2
console.log(compteur2()); // 1 (compteur indépendant)
console.log(compteur1()); // 3

// Pourquoi c'est utile ? Encapsulation et données privées !
function creerBanque() {
  let solde = 100; // Variable privée, inaccessible de l'extérieur
  
  return {
    deposer: function(montant) {
      solde += montant;
      return solde;
    },
    retirer: function(montant) {
      if (montant <= solde) {
        solde -= montant;
        return solde;
      } else {
        return "Solde insuffisant";
      }
    },
    consulter: function() {
      return solde;
    }
  };
}

let monCompte = creerBanque();
console.log(monCompte.consulter()); // 100
console.log(monCompte.deposer(50)); // 150
console.log(monCompte.retirer(30)); // 120
// console.log(solde); // Erreur ! solde n'est pas accessible

7. Tableaux : gérer des listes de données

Création et accès aux éléments

// Différentes façons de créer un tableau
let fruits = ["pomme", "banane", "orange"];
let nombres = [1, 2, 3, 4, 5];
let mixte = ["texte", 42, true, null];
let vide = [];

// Accès aux éléments (index commence à 0)
console.log(fruits[0]); // "pomme"
console.log(fruits[1]); // "banane"
console.log(fruits[2]); // "orange"
console.log(fruits[3]); // undefined (n'existe pas)

// Propriétés utiles
console.log(fruits.length); // 3
console.log(fruits[fruits.length - 1]); // "orange" (dernier élément)

Méthodes pour modifier les tableaux

let animaux = ["chat", "chien"];

// Ajouter à la fin
animaux.push("oiseau");
console.log(animaux); // ["chat", "chien", "oiseau"]

// Ajouter au début
animaux.unshift("poisson");
console.log(animaux); // ["poisson", "chat", "chien", "oiseau"]

// Retirer le dernier élément
let dernier = animaux.pop();
console.log(dernier); // "oiseau"
console.log(animaux); // ["poisson", "chat", "chien"]

// Retirer le premier élément
let premier = animaux.shift();
console.log(premier); // "poisson"
console.log(animaux); // ["chat", "chien"]

// Trouver l'index d'un élément
let index = animaux.indexOf("chien");
console.log(index); // 1

// Supprimer des éléments par index
animaux.splice(1, 1); // Supprime 1 élément à partir de l'index 1
console.log(animaux); // ["chat"]

Méthodes fonctionnelles modernes

Ces méthodes ne modifient pas le tableau original mais en retournent un nouveau.

let nombres = [1, 2, 3, 4, 5];

// map() : transformer chaque élément
let doubles = nombres.map(n => n * 2);
console.log(doubles); // [2, 4, 6, 8, 10]

// filter() : garder seulement certains éléments
let pairs = nombres.filter(n => n % 2 === 0);
console.log(pairs); // [2, 4]

// reduce() : réduire à une seule valeur
let somme = nombres.reduce((total, n) => total + n, 0);
console.log(somme); // 15

// find() : trouver le premier élément qui correspond
let premierGrand = nombres.find(n => n > 3);
console.log(premierGrand); // 4

// some() : vérifier si au moins un élément correspond
let aDesGrands = nombres.some(n => n > 10);
console.log(aDesGrands); // false

// every() : vérifier si tous les éléments correspondent
let tousPositifs = nombres.every(n => n > 0);
console.log(tousPositifs); // true

Exemple pratique : gestion d'une liste de tâches

let taches = [
  { id: 1, texte: "Apprendre JavaScript", termine: false },
  { id: 2, texte: "Faire les courses", termine: true },
  { id: 3, texte: "Appeler le médecin", termine: false }
];

// Afficher toutes les tâches non terminées
let tachesRestantes = taches.filter(tache => !tache.termine);
console.log("Tâches restantes :", tachesRestantes);

// Marquer une tâche comme terminée
function terminerTache(id) {
  let tache = taches.find(t => t.id === id);
  if (tache) {
    tache.termine = true;
    console.log(`Tâche "${tache.texte}" terminée !`);
  }
}

terminerTache(1);

// Compter les tâches terminées
let nombreTerminees = taches.filter(t => t.termine).length;
console.log(`${nombreTerminees} tâches terminées sur ${taches.length}`);

8. Objets : structurer des données complexes

Création et utilisation d'objets

// Objet littéral
let personne = {
  prenom: "Julien",
  nom: "Dupont",
  age: 35,
  ville: "Paris",
  competences: ["JavaScript", "QA", "DevOps"],
  
  // Méthode (fonction dans un objet)
  sePresenter: function() {
    return `Je m'appelle ${this.prenom} ${this.nom} et j'ai ${this.age} ans.`;
  },
  
  // Méthode avec syntaxe courte (ES6)
  direBonjour() {
    return `Bonjour, je suis ${this.prenom} !`;
  }
};

// Accès aux propriétés
console.log(personne.prenom); // "Julien"
console.log(personne["nom"]); // "Dupont"

// Appel de méthodes
console.log(personne.sePresenter());
console.log(personne.direBonjour());

// Modifier des propriétés
personne.age = 36;
personne["ville"] = "Lyon";

// Ajouter de nouvelles propriétés
personne.email = "julien@example.com";
personne.calculerAnneeNaissance = function() {
  return new Date().getFullYear() - this.age;
};

Le mot-clé 'this'

this fait référence à l'objet qui appelle la méthode. C'est crucial pour accéder aux autres propriétés de l'objet.

let voiture = {
  marque: "Toyota",
  modele: "Corolla", 
  annee: 2020,
  
  getDescription: function() {
    // 'this' fait référence à l'objet voiture
    return `${this.marque} ${this.modele} de ${this.annee}`;
  },
  
  // Attention avec les fonctions fléchées !
  getDescriptionFleche: () => {
    // 'this' ne fonctionne pas comme attendu avec les fonctions fléchées
    return `${this.marque} ${this.modele} de ${this.annee}`; // undefined undefined undefined
  }
};

console.log(voiture.getDescription()); // "Toyota Corolla de 2020"
console.log(voiture.getDescriptionFleche()); // "undefined undefined undefined"

Destructuring : extraire des propriétés facilement

let personne = {
  prenom: "Alice",
  nom: "Martin",
  age: 28,
  ville: "Paris"
};

// Ancienne façon
let prenom = personne.prenom;
let nom = personne.nom;

// Destructuring (moderne)
let { prenom, nom, age } = personne;
console.log(prenom, nom, age); // "Alice" "Martin" 28

// Avec renommage
let { prenom: prenomUtilisateur, ville: residence } = personne;
console.log(prenomUtilisateur, residence); // "Alice" "Paris"

// Avec valeurs par défaut
let { prenom, profession = "Non spécifié" } = personne;
console.log(profession); // "Non spécifié"

Classes ES6 : orientation objet moderne

class Personne {
  constructor(prenom, nom, age) {
    this.prenom = prenom;
    this.nom = nom;
    this.age = age;
  }
  
  sePresenter() {
    return `Je suis ${this.prenom} ${this.nom}, ${this.age} ans.`;
  }
  
  anniversaire() {
    this.age++;
    console.log(`Joyeux anniversaire ! Maintenant ${this.age} ans.`);
  }
  
  // Getter : propriété calculée
  get nomComplet() {
    return `${this.prenom} ${this.nom}`;
  }
  
  // Setter : contrôler l'assignation
  set nouveauAge(age) {
    if (age >= 0 && age <= 150) {
      this.age = age;
    } else {
      console.log("Âge invalide !");
    }
  }
}

// Utilisation
let julien = new Personne("Julien", "Dupont", 35);
console.log(julien.sePresenter());
console.log(julien.nomComplet); // Getter
julien.nouveauAge = 36; // Setter
julien.anniversaire();

// Héritage
class Employe extends Personne {
  constructor(prenom, nom, age, poste) {
    super(prenom, nom, age); // Appel du constructeur parent
    this.poste = poste;
  }
  
  sePresenter() {
    return super.sePresenter() + ` Je travaille comme ${this.poste}.`;
  }
}

let alice = new Employe("Alice", "Martin", 28, "Développeuse");
console.log(alice.sePresenter()); // "Je suis Alice Martin, 28 ans. Je travaille comme Développeuse."

9. Manipulation du DOM : interagir avec la page web

Qu'est-ce que le DOM ?

Le DOM (Document Object Model) est la représentation de votre page HTML sous forme d'arbre d'objets. JavaScript peut modifier cette structure en temps réel pour créer des pages interactives.

Sélectionner des éléments

// Par ID (le plus spécifique)
let bouton = document.getElementById("monBouton");

// Par classe CSS
let elements = document.getElementsByClassName("maClasse");
let premier = elements[0]; // Retourne une collection, prendre le premier

// Par nom de balise
let paragraphes = document.getElementsByTagName("p");

// Sélecteurs CSS modernes (recommandés)
let bouton2 = document.querySelector("#monBouton"); // Premier élément trouvé
let tousLesBoutons = document.querySelectorAll(".bouton"); // Tous les éléments

// Exemples pratiques
let titre = document.querySelector("h1");
let listeElements = document.querySelectorAll("li");
let premierInput = document.querySelector("input[type='text']");

Modifier le contenu

// HTML de départ
// <p id="message">Texte original</p>
// <div id="contenu"><span>Contenu HTML</span></div>

let message = document.getElementById("message");

// Modifier le texte (sécurisé)
message.textContent = "Nouveau texte";

// Modifier le HTML (attention aux failles XSS !)
let contenu = document.getElementById("contenu");
contenu.innerHTML = "<strong>Texte en gras</strong>";

// Modifier les attributs
let image = document.querySelector("img");
image.src = "nouvelle-image.jpg";
image.alt = "Description de la nouvelle image";

// Modifier les classes CSS
let element = document.querySelector(".boite");
element.classList.add("nouvelle-classe");
element.classList.remove("ancienne-classe");
element.classList.toggle("active"); // Ajoute si absent, retire si présent

// Modifier les styles directement
element.style.color = "red";
element.style.backgroundColor = "yellow";
element.style.fontSize = "20px";

Créer et supprimer des éléments

// Créer un nouvel élément
let nouveauParagraphe = document.createElement("p");
nouveauParagraphe.textContent = "Je suis un nouveau paragraphe";
nouveauParagraphe.classList.add("nouveau");

// L'ajouter à la page
let conteneur = document.querySelector(".conteneur");
conteneur.appendChild(nouveauParagraphe);

// Insérer à une position spécifique
let premierEnfant = conteneur.firstElementChild;
conteneur.insertBefore(nouveauParagraphe, premierEnfant);

// Supprimer un élément
let elementASupprimer = document.querySelector(".a-supprimer");
elementASupprimer.remove(); // Méthode moderne

// Ou avec l'ancienne méthode
// elementASupprimer.parentNode.removeChild(elementASupprimer);

// Exemple pratique : créer une liste dynamique
function ajouterElement(texte) {
  let liste = document.getElementById("maListe");
  let nouvelItem = document.createElement("li");
  nouvelItem.textContent = texte;
  
  // Ajouter un bouton de suppression
  let boutonSuppr = document.createElement("button");
  boutonSuppr.textContent = "Supprimer";
  boutonSuppr.onclick = function() {
    nouvelItem.remove();
  };
  
  nouvelItem.appendChild(boutonSuppr);
  liste.appendChild(nouvelItem);
}

ajouterElement("Premier élément");
ajouterElement("Deuxième élément");

10. Gestion des événements : réagir aux actions utilisateur

Qu'est-ce qu'un événement ?

Un événement se produit quand l'utilisateur interagit avec la page : clic, saisie clavier, mouvement de souris, etc. JavaScript peut "écouter" ces événements et exécuter du code en réponse.

Ajouter des écouteurs d'événements

// HTML : <button id="monBouton">Cliquez-moi</button>

let bouton = document.getElementById("monBouton");

// Méthode moderne (recommandée)
bouton.addEventListener("click", function() {
  alert("Bouton cliqué !");
});

// Avec fonction fléchée
bouton.addEventListener("click", () => {
  console.log("Clic détecté");
});

// Avec fonction nommée (réutilisable)
function gererClic() {
  console.log("Fonction de gestion du clic");
}
bouton.addEventListener("click", gererClic);

// Retirer un écouteur
bouton.removeEventListener("click", gererClic);

Événements courants

// Événements de souris
element.addEventListener("click", () => console.log("Clic"));
element.addEventListener("dblclick", () => console.log("Double-clic"));
element.addEventListener("mouseenter", () => console.log("Souris entre"));
element.addEventListener("mouseleave", () => console.log("Souris sort"));

// Événements de clavier
document.addEventListener("keydown", (event) => {
  console.log("Touche pressée :", event.key);
  if (event.key === "Enter") {
    console.log("Entrée pressée !");
  }
});

// Événements de formulaire
let input = document.querySelector("input");
input.addEventListener("input", (event) => {
  console.log("Valeur saisie :", event.target.value);
});

input.addEventListener("focus", () => console.log("Focus sur l'input"));
input.addEventListener("blur", () => console.log("Focus perdu"));

// Événement de soumission de formulaire
let formulaire = document.querySelector("form");
formulaire.addEventListener("submit", (event) => {
  event.preventDefault(); // Empêche l'envoi normal du formulaire
  console.log("Formulaire soumis");
});

Objet événement

Chaque événement fournit un objet contenant des informations utiles.

document.addEventListener("click", (event) => {
  console.log("Élément cliqué :", event.target);
  console.log("Position X :", event.clientX);
  console.log("Position Y :", event.clientY);
  console.log("Type d'événement :", event.type);
  
  // Empêcher le comportement par défaut
  if (event.target.tagName === "A") {
    event.preventDefault(); // Empêche le lien de fonctionner
    console.log("Clic sur lien intercepté");
  }
});

// Événement clavier avec détails
document.addEventListener("keydown", (event) => {
  console.log("Touche :", event.key);
  console.log("Code :", event.code);
  console.log("Ctrl pressé :", event.ctrlKey);
  console.log("Alt pressé :", event.altKey);
  console.log("Shift pressé :", event.shiftKey);
});

Délégation d'événements

Technique puissante pour gérer les événements sur des éléments qui n'existent pas encore.

// Au lieu d'ajouter un écouteur sur chaque bouton
// On ajoute un seul écouteur sur le conteneur parent
let conteneur = document.getElementById("conteneur");

conteneur.addEventListener("click", (event) => {
  if (event.target.classList.contains("bouton-supprimer")) {
    // Un bouton de suppression a été cliqué
    let element = event.target.parentElement;
    element.remove();
  } else if (event.target.classList.contains("bouton-modifier")) {
    // Un bouton de modification a été cliqué
    console.log("Modification demandée");
  }
});

// Maintenant, même les boutons ajoutés dynamiquement fonctionneront !

11. Programmation asynchrone : gérer le temps

Pourquoi l'asynchrone ?

JavaScript est mono-thread, mais les opérations comme les requêtes réseau ou les timers ne doivent pas bloquer l'interface. L'asynchrone permet d'exécuter du code "plus tard" sans figer la page.

setTimeout et setInterval

// Exécuter une fonction après un délai
setTimeout(() => {
  console.log("Exécuté après 2 secondes");
}, 2000);

// Fonction avec paramètres
function afficherMessage(message, auteur) {
  console.log(`${message} - ${auteur}`);
}

setTimeout(afficherMessage, 1000, "Bonjour", "JavaScript");

// Répéter une fonction à intervalles réguliers
let compteur = 0;
let intervalle = setInterval(() => {
  compteur++;
  console.log(`Compteur : ${compteur}`);
  
  if (compteur >= 5) {
    clearInterval(intervalle); // Arrêter la répétition
    console.log("Terminé !");
  }
}, 1000);

// Annuler un timeout
let timeout = setTimeout(() => {
  console.log("Ceci ne s'exécutera pas");
}, 5000);

clearTimeout(timeout); // Annule le timeout

Promises : gérer l'asynchrone proprement

Les Promises représentent une valeur qui sera disponible dans le futur. Elles permettent d'éviter le "callback hell".

// Créer une Promise
function attendre(duree) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Terminé après ${duree}ms`);
    }, duree);
  });
}

// Utiliser une Promise
attendre(2000)
  .then(resultat => {
    console.log(resultat); // "Terminé après 2000ms"
    return attendre(1000); // Chaîner une autre Promise
  })
  .then(resultat => {
    console.log(resultat); // "Terminé après 1000ms"
  })
  .catch(erreur => {
    console.error("Erreur :", erreur);
  });

// Promise qui peut échouer
function lancerDe() {
  return new Promise((resolve, reject) => {
    let resultat = Math.floor(Math.random() * 6) + 1;
    
    if (resultat >= 4) {
      resolve(`Gagné ! Résultat : ${resultat}`);
    } else {
      reject(`Perdu ! Résultat : ${resultat}`);
    }
  });
}

lancerDe()
  .then(message => console.log("Succès :", message))
  .catch(erreur => console.log("Échec :", erreur));

async/await : syntaxe moderne

async/await rend le code asynchrone plus lisible, comme s'il était synchrone.

// Fonction asynchrone
async function exempleAsync() {
  try {
    console.log("Début");
    
    let resultat1 = await attendre(1000);
    console.log(resultat1);
    
    let resultat2 = await attendre(500);
    console.log(resultat2);
    
    console.log("Fin");
  } catch (erreur) {
    console.error("Erreur attrapée :", erreur);
  }
}

exempleAsync();

// Exemple pratique : requête API
async function obtenirUtilisateur(id) {
  try {
    let response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
    
    if (!response.ok) {
      throw new Error(`Erreur HTTP : ${response.status}`);
    }
    
    let utilisateur = await response.json();
    console.log("Utilisateur :", utilisateur.name);
    return utilisateur;
  } catch (erreur) {
    console.error("Impossible de récupérer l'utilisateur :", erreur);
  }
}

obtenirUtilisateur(1);

// Exécuter plusieurs Promises en parallèle
async function exempleParallele() {
  try {
    let [user1, user2, user3] = await Promise.all([
      obtenirUtilisateur(1),
      obtenirUtilisateur(2),
      obtenirUtilisateur(3)
    ]);
    
    console.log("Tous les utilisateurs récupérés !");
  } catch (erreur) {
    console.error("Au moins une requête a échoué :", erreur);
  }
}

12. Exercices pratiques complets

Exercice 1 : Calculatrice interactive

<!-- HTML -->
<div class="calculatrice">
  <input type="text" id="affichage" readonly>
  <div class="boutons">
    <button onclick="effacer()">C</button>
    <button onclick="ajouterChiffre('/')">÷</button>
    <button onclick="ajouterChiffre('*')">×</button>
    <button onclick="supprimerDernier()">←</button>
    
    <button onclick="ajouterChiffre('7')">7</button>
    <button onclick="ajouterChiffre('8')">8</button>
    <button onclick="ajouterChiffre('9')">9</button>
    <button onclick="ajouterChiffre('-')">-</button>
    
    <button onclick="ajouterChiffre('4')">4</button>
    <button onclick="ajouterChiffre('5')">5</button>
    <button onclick="ajouterChiffre('6')">6</button>
    <button onclick="ajouterChiffre('+')">+</button>
    
    <button onclick="ajouterChiffre('1')">1</button>
    <button onclick="ajouterChiffre('2')">2</button>
    <button onclick="ajouterChiffre('3')">3</button>
    <button onclick="calculer()" rowspan="2">=</button>
    
    <button onclick="ajouterChiffre('0')" colspan="2">0</button>
    <button onclick="ajouterChiffre('.')">.</button>
  </div>
</div>
// JavaScript pour la calculatrice
let affichage = document.getElementById('affichage');
let calcul = '';

function ajouterChiffre(valeur) {
  calcul += valeur;
  affichage.value = calcul;
}

function effacer() {
  calcul = '';
  affichage.value = '';
}

function supprimerDernier() {
  calcul = calcul.slice(0, -1);
  affichage.value = calcul;
}

function calculer() {
  try {
    // ATTENTION : eval() est dangereux en production !
    // Ici c'est pour l'exemple, en vrai il faut parser proprement
    let resultat = eval(calcul);
    affichage.value = resultat;
    calcul = resultat.toString();
  } catch (erreur) {
    affichage.value = 'Erreur';
    calcul = '';
  }
}

// Gestion du clavier
document.addEventListener('keydown', (event) => {
  if (event.key >= '0' && event.key <= '9') {
    ajouterChiffre(event.key);
  } else if (['+', '-', '*', '/'].includes(event.key)) {
    ajouterChiffre(event.key);
  } else if (event.key === 'Enter' || event.key === '=') {
    calculer();
  } else if (event.key === 'Escape') {
    effacer();
  } else if (event.key === 'Backspace') {
    supprimerDernier();
  }
});

Exercice 2 : Application météo avec API

<!-- HTML -->
<div class="meteo-app">
  <h1>Météo App</h1>
  <div class="recherche">
    <input type="text" id="villeInput" placeholder="Entrez une ville...">
    <button id="rechercherBtn">Rechercher</button>
  </div>
  <div id="loader" class="hidden">Chargement...</div>
  <div id="resultat" class="hidden">
    <h2 id="nomVille"></h2>
    <div id="temperature"></div>
    <div id="description"></div>
    <div id="details"></div>
  </div>
  <div id="erreur" class="hidden"></div>
</div>
// JavaScript pour l'app météo
class MeteoApp {
  constructor() {
    this.apiKey = 'VOTRE_CLE_API'; // Remplacer par votre clé OpenWeatherMap
    this.baseUrl = 'https://api.openweathermap.org/data/2.5/weather';
    
    this.villeInput = document.getElementById('villeInput');
    this.rechercherBtn = document.getElementById('rechercherBtn');
    this.loader = document.getElementById('loader');
    this.resultat = document.getElementById('resultat');
    this.erreur = document.getElementById('erreur');
    
    this.initEventListeners();
  }
  
  initEventListeners() {
    this.rechercherBtn.addEventListener('click', () => this.rechercherMeteo());
    
    this.villeInput.addEventListener('keypress', (event) => {
      if (event.key === 'Enter') {
        this.rechercherMeteo();
      }
    });
  }
  
  async rechercherMeteo() {
    let ville = this.villeInput.value.trim();
    
    if (!ville) {
      this.afficherErreur('Veuillez entrer le nom d\'une ville');
      return;
    }
    
    this.afficherLoader();
    
    try {
      let url = `${this.baseUrl}?q=${ville}&appid=${this.apiKey}&units=metric&lang=fr`;
      let response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`Ville non trouvée (${response.status})`);
      }
      
      let donnees = await response.json();
      this.afficherResultat(donnees);
      
    } catch (erreur) {
      this.afficherErreur(`Erreur : ${erreur.message}`);
    }
  }
  
  afficherLoader() {
    this.cacher([this.resultat, this.erreur]);
    this.montrer([this.loader]);
  }
  
  afficherResultat(donnees) {
    document.getElementById('nomVille').textContent = 
      `${donnees.name}, ${donnees.sys.country}`;
    
    document.getElementById('temperature').textContent = 
      `${Math.round(donnees.main.temp)}°C`;
    
    document.getElementById('description').textContent = 
      donnees.weather[0].description;
    
    document.getElementById('details').innerHTML = `
      <p>Ressenti : ${Math.round(donnees.main.feels_like)}°C</p>
      <p>Humidité : ${donnees.main.humidity}%</p>
      <p>Vent : ${donnees.wind.speed} m/s</p>
    `;
    
    this.cacher([this.loader, this.erreur]);
    this.montrer([this.resultat]);
  }
  
  afficherErreur(message) {
    this.erreur.textContent = message;
    this.cacher([this.loader, this.resultat]);
    this.montrer([this.erreur]);
  }
  
  cacher(elements) {
    elements.forEach(el => el.classList.add('hidden'));
  }
  
  montrer(elements) {
    elements.forEach(el => el.classList.remove('hidden'));
  }
}

// Initialiser l'application
let app = new MeteoApp();

Exercice 3 : Jeu de mémoire (Memory)

// Jeu de mémoire complet
class JeuMemoire {
  constructor() {
    this.cartes = ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼'];
    this.plateau = [...this.cartes, ...this.cartes]; // Doubler pour les paires
    this.cartesRetournees = [];
    this.pairesTrouvees = 0;
    this.tentatives = 0;
    this.jeuActif = false;
    
    this.initPlateau();
    this.melangerCartes();
    this.creerInterface();
  }
  
  initPlateau() {
    this.container = document.getElementById('jeu-memoire') || this.creerContainer();
  }
  
  creerContainer() {
    let container = document.createElement('div');
    container.id = 'jeu-memoire';
    container.innerHTML = `
      <h2>Jeu de Mémoire</h2>
      <div id="stats">
        <span>Tentatives : <span id="compteur-tentatives">0</span></span>
        <button id="nouveau-jeu">Nouveau Jeu</button>
      </div>
      <div id="plateau"></div>
    `;
    document.body.appendChild(container);
    return container;
  }
  
  melangerCartes() {
    for (let i = this.plateau.length - 1; i > 0; i--) {
      let j = Math.floor(Math.random() * (i + 1));
      [this.plateau[i], this.plateau[j]] = [this.plateau[j], this.plateau[i]];
    }
  }
  
  creerInterface() {
    let plateauEl = document.getElementById('plateau');
    plateauEl.innerHTML = '';
    plateauEl.style.cssText = `
      display: grid;
      grid-template-columns: repeat(4, 100px);
      gap: 10px;
      margin: 20px 0;
    `;
    
    this.plateau.forEach((carte, index) => {
      let carteEl = document.createElement('div');
      carteEl.className = 'carte';
      carteEl.dataset.index = index;
      carteEl.dataset.valeur = carte;
      carteEl.textContent = '?';
      carteEl.style.cssText = `
        width: 100px;
        height: 100px;
        background: #3498db;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 24px;
        cursor: pointer;
        border-radius: 8px;
        transition: transform 0.2s;
      `;
      
      carteEl.addEventListener('click', () => this.retournerCarte(carteEl));
      plateauEl.appendChild(carteEl);
    });
    
    document.getElementById('nouveau-jeu').addEventListener('click', () => {
      this.nouveauJeu();
    });
    
    this.jeuActif = true;
  }
  
  retournerCarte(carteEl) {
    if (!this.jeuActif || 
        carteEl.classList.contains('retournee') || 
        this.cartesRetournees.length >= 2) {
      return;
    }
    
    carteEl.textContent = carteEl.dataset.valeur;
    carteEl.style.background = '#2ecc71';
    carteEl.classList.add('retournee');
    this.cartesRetournees.push(carteEl);
    
    if (this.cartesRetournees.length === 2) {
      this.tentatives++;
      document.getElementById('compteur-tentatives').textContent = this.tentatives;
      
      setTimeout(() => this.verifierPaire(), 1000);
    }
  }
  
  verifierPaire() {
    let [carte1, carte2] = this.cartesRetournees;
    
    if (carte1.dataset.valeur === carte2.dataset.valeur) {
      // Paire trouvée
      carte1.style.background = '#f39c12';
      carte2.style.background = '#f39c12';
      carte1.classList.add('trouvee');
      carte2.classList.add('trouvee');
      
      this.pairesTrouvees++;
      
      if (this.pairesTrouvees === this.cartes.length) {
        setTimeout(() => {
          alert(`Félicitations ! Jeu terminé en ${this.tentatives} tentatives !`);
        }, 500);
        this.jeuActif = false;
      }
    } else {
      // Pas de paire
      carte1.textContent = '?';
      carte2.textContent = '?';
      carte1.style.background = '#3498db';
      carte2.style.background = '#3498db';
      carte1.classList.remove('retournee');
      carte2.classList.remove('retournee');
    }
    
    this.cartesRetournees = [];
  }
  
  nouveauJeu() {
    this.pairesTrouvees = 0;
    this.tentatives = 0;
    this.cartesRetournees = [];
    document.getElementById('compteur-tentatives').textContent = '0';
    
    this.melangerCartes();
    this.creerInterface();
  }
}

// Démarrer le jeu
let jeu = new JeuMemoire();
🎯 Félicitations ! Vous avez maintenant une base solide en JavaScript. Ces exercices combinent tous les concepts vus : variables, fonctions, objets, DOM, événements, asynchrone, et classes. Continuez à pratiquer en créant vos propres projets !

🔍 Outils Navigateur & DOM — Guide Complet

Cette section est une véritable mini-formation sur les DevTools (outils de développement intégrés aux navigateurs). Elle s’adresse à ceux qui débutent et veulent comprendre pas à pas comment utiliser ces outils pour inspecter le DOM, analyser les performances, déboguer le JavaScript, tester la sécurité et simuler des environnements différents.

🎯 Objectif : devenir autonome avec Chrome DevTools (valable aussi pour Firefox, Edge, Safari).

1. Introduction aux DevTools

Les DevTools sont accessibles directement dans le navigateur via :

  • F12 ou Ctrl + Shift + I (Windows/Linux)
  • Cmd + Option + I (Mac)
  • Clic droit → Inspecter

Ils ouvrent un panneau riche divisé en plusieurs onglets : Elements, Console, Network, Sources, Performance, Application, et plus encore.

💡 Conseil : gardez DevTools ouvert pendant vos tests, cela devient vite un réflexe.

2. Inspecteur DOM (onglet Elements)

L’onglet Elements permet d’explorer la structure HTML générée. On peut :

  • Voir l’arborescence du DOM (balises imbriquées).
  • Modifier du HTML/CSS en direct pour tester un correctif.
  • Voir les règles CSS appliquées à un élément (héritage, cascade).
<div class="card">
  <h2>Produit</h2>
  <p>Prix : 10€</p>
</div>

👉 En inspectant <div class="card">, vous pouvez modifier directement le texte ou les styles, sans toucher au code source.

⚡ Cas pratique : corriger un alignement CSS cassé directement dans DevTools pour vérifier une hypothèse avant de changer le code source.

3. Console JavaScript

L’onglet Console est votre terrain de jeu JS. On peut :

  • Exécuter du code JS à la volée.
  • Lire les erreurs (stack trace).
  • Afficher des logs formatés.
console.log("Message simple");
console.warn("Attention !");
console.error("Erreur critique");
console.table([{nom:"Julien", role:"QA"}, {nom:"Anna", role:"Dev"}]);

👉 Pendant vos tests, utilisez la console pour vérifier rapidement la valeur d’une variable, simuler une fonction ou tester une API.

4. Sources & Debugger

L’onglet Sources contient tous les fichiers JS/CSS/HTML chargés. On peut :

  • Placer des breakpoints dans le code JS.
  • Suivre l’exécution pas à pas.
  • Inspecter les variables locales.
function calcule(a, b) {
  let somme = a + b;
  return somme;
}
calcule(2, 3);

👉 Si vous placez un breakpoint sur return somme;, le navigateur s’arrête et vous montre la valeur de somme.

5. Réseau (onglet Network)

L’onglet Network liste toutes les requêtes HTTP. Très utile pour :

  • Voir les URLs appelées par le front-end.
  • Analyser les temps de réponse.
  • Vérifier les en-têtes HTTP (Content-Type, CORS, sécurité).
  • Inspecter les réponses JSON des APIs.
⚡ Exemple QA : vérifier qu’une API renvoie bien un code 200 OK et que la payload JSON est correcte.

6. Performance & Lighthouse

Deux outils essentiels :

  • Performance → enregistre une timeline pour détecter les ralentissements.
  • Lighthouse → génère un audit complet : performance, SEO, accessibilité, PWA.
🚀 Ces outils permettent d’identifier un script trop lourd ou une image non optimisée.

7. Stockage & Application

L’onglet Application permet d’explorer :

  • localStorage et sessionStorage.
  • Cookies (Secure, HttpOnly, SameSite).
  • IndexedDB (bases locales).
  • Cache & Service Workers.
// Simuler un stockage local
localStorage.setItem("theme", "dark");
console.log(localStorage.getItem("theme"));

8. Simulations & Emulation

DevTools permet de tester différents contextes :

  • Mode responsive (mobile, tablette, desktop).
  • Throttling réseau (3G lente, offline).
  • Emulation de géolocalisation.
⚡ Cas pratique QA : simuler une perte de connexion pour tester la résilience d’une application.

9. Vérification Sécurité

DevTools permet aussi de vérifier :

  • Les certificats HTTPS.
  • Les en-têtes CSP (Content Security Policy).
  • Les failles XSS via l’inspection DOM.
// Exemple XSS
document.body.innerHTML = "<img src=x onerror=alert('XSS')>";

👉 Dans un vrai site sécurisé, ce type d’injection doit être bloqué.

10. Exercices pratiques

  1. Inspecter un bouton sur une page et modifier sa couleur en direct.
  2. Placer un breakpoint dans un script et observer la valeur des variables.
  3. Analyser une API dans l’onglet Network et vérifier son JSON.
  4. Lancer un audit Lighthouse et noter les améliorations proposées.
  5. Simuler une connexion 3G et observer l’impact sur le temps de chargement.
🎯 Après ces exercices, vous saurez utiliser DevTools comme un pro pour analyser, corriger et valider vos tests web.

🧩 Projet final – Exercices guidés

Dans cette section, nous allons réaliser un projet complet, étape par étape. Vous n’avez rien à inventer : chaque ligne de code est donnée, expliquée et fonctionnelle. À la fin, vous aurez une petite application Todo List interactive que vous pourrez tester dans votre navigateur.

1. Introduction

Le but de cet exercice est de vous montrer comment assembler tout ce que vous avez appris :

  • HTML pour la structure
  • CSS pour le style et la présentation
  • JavaScript pour l’interactivité et la logique
🎯 Objectif pédagogique : comprendre comment HTML, CSS et JS s’articulent dans une mini-application.

2. Préparation

Avant de coder, comprenons le fonctionnement attendu :

  1. L’utilisateur saisit une tâche dans un champ texte.
  2. Il clique sur le bouton Ajouter.
  3. La tâche apparaît dans une liste sous le formulaire.
  4. Chaque tâche ajoutée peut être cochée comme terminée, ou supprimée.

Cela implique 3 grandes zones dans notre page :

  • Un champ texte + bouton → pour ajouter des tâches.
  • Une liste (<ul>) → qui contiendra les tâches.
  • Du JavaScript → pour gérer les clics et manipuler le DOM.

3. Code complet à copier

Copiez-collez le code suivant dans un fichier todo.html. Ouvrez-le dans votre navigateur et votre application sera fonctionnelle.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Ma Todo List</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background: #f9fafb;
      color: #333;
      display: flex;
      justify-content: center;
      align-items: flex-start;
      padding: 2rem;
    }
    .app {
      background: white;
      padding: 2rem;
      border-radius: 12px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.1);
      width: 400px;
    }
    h1 {
      text-align: center;
      margin-bottom: 1rem;
      color: #004a7c;
    }
    form {
      display: flex;
      gap: 0.5rem;
    }
    input {
      flex: 1;
      padding: 0.6rem;
      border: 1px solid #ccc;
      border-radius: 6px;
    }
    button {
      padding: 0.6rem 1rem;
      border: none;
      border-radius: 6px;
      background: #005b96;
      color: white;
      cursor: pointer;
      transition: background 0.2s;
    }
    button:hover { background: #004a7c; }
    ul {
      list-style: none;
      margin-top: 1rem;
      padding: 0;
    }
    li {
      display: flex;
      justify-content: space-between;
      align-items: center;
      background: #f1f5f9;
      padding: 0.5rem 1rem;
      border-radius: 6px;
      margin-bottom: 0.5rem;
    }
    li.done { text-decoration: line-through; color: gray; }
    .delete {
      background: red;
      color: white;
      border: none;
      border-radius: 4px;
      padding: 0.3rem 0.6rem;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div class="app">
    <h1>Ma Todo List</h1>
    <form id="todoForm">
      <input id="taskInput" type="text" placeholder="Nouvelle tâche..." required>
      <button type="submit">Ajouter</button>
    </form>
    <ul id="taskList"></ul>
  </div>

  <script>
    const form = document.getElementById("todoForm");
    const input = document.getElementById("taskInput");
    const list = document.getElementById("taskList");

    form.addEventListener("submit", (e) => {
      e.preventDefault(); // Empêche le rechargement
      if (input.value.trim() !== "") {
        const li = document.createElement("li");
        li.textContent = input.value;

        // Bouton supprimer
        const delBtn = document.createElement("button");
        delBtn.textContent = "X";
        delBtn.className = "delete";
        delBtn.onclick = () => li.remove();

        // Marquer comme fait
        li.onclick = () => li.classList.toggle("done");

        li.appendChild(delBtn);
        list.appendChild(li);
        input.value = "";
      }
    });
  </script>
</body>
</html>

4. Explication détaillée

Décomposons ce que fait ce code :

  • HTML → structure avec formulaire + liste vide.
  • CSS → styles modernes, boutons bleus, tâches grisées quand cochées.
  • JS → ajoute un <li> à chaque soumission du formulaire, gère la suppression et le marquage comme terminé.
✅ Résultat : vous obtenez une Todo List complète et fonctionnelle. Coller → lancer → utiliser !

🧪 Tests Web Automatisés - Guide Expert

Cette section couvre les outils et méthodologies modernes pour l'automatisation des tests web. Nous explorerons les trois frameworks dominants du marché avec leurs avantages, inconvénients et cas d'usage optimaux, accompagnés d'exemples concrets et de patterns d'architecture.

1. Paysage des tests web automatisés

Pourquoi automatiser les tests web ?

L'automatisation des tests web permet de garantir la qualité et la stabilité des applications dans un contexte de livraison continue. Les bénéfices incluent :

  • Régression continue : détection précoce des régressions
  • Couverture étendue : tests impossibles à réaliser manuellement
  • Feedback rapide : intégration dans les pipelines CI/CD
  • Reproductibilité : tests identiques à chaque exécution
  • Parallélisation : exécution simultanée sur multiple environnements

Architecture des tests web

Les tests web s'appuient sur le protocole WebDriver qui permet de contrôler un navigateur programmatiquement. L'architecture typique comprend :

Test Script → WebDriver → Browser Driver → Browser → Application
🏗️ Pattern architectural : Privilégiez toujours le Page Object Model pour maintenir la séparation des responsabilités et faciliter la maintenance.

2. Selenium WebDriver - Le standard historique

Positionnement

Selenium reste la référence pour l'automatisation web multi-navigateurs, particulièrement adapté aux environnements legacy et aux équipes Java/.NET.

  • Avantages : Maturité, écosystème riche, support multi-langages
  • Inconvénients : Configuration complexe, API verbose, debugging difficile
  • Cas d'usage : Tests de régression, migration legacy, grilles de test

Configuration et premiers pas

# Installation
pip install selenium

# Configuration WebDriver
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options

class WebDriverSetup:
    @staticmethod
    def get_chrome_driver():
        options = Options()
        options.add_argument("--headless")  # Mode sans interface
        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument("--window-size=1920,1080")
        
        return webdriver.Chrome(options=options)

Pattern Page Object Model

class LoginPage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
        
    # Locators (sélecteurs centralisés)
    EMAIL_INPUT = (By.ID, "email")
    PASSWORD_INPUT = (By.ID, "password")
    LOGIN_BUTTON = (By.CSS_SELECTOR, "button[type='submit']")
    ERROR_MESSAGE = (By.CLASS_NAME, "error-message")
    
    def navigate_to_login(self):
        self.driver.get("https://app.example.com/login")
        return self
    
    def enter_credentials(self, email, password):
        email_field = self.wait.until(EC.presence_of_element_located(self.EMAIL_INPUT))
        email_field.clear()
        email_field.send_keys(email)
        
        password_field = self.driver.find_element(*self.PASSWORD_INPUT)
        password_field.clear()
        password_field.send_keys(password)
        return self
    
    def click_login(self):
        login_btn = self.wait.until(EC.element_to_be_clickable(self.LOGIN_BUTTON))
        login_btn.click()
        return self
    
    def get_error_message(self):
        try:
            error_element = self.wait.until(EC.presence_of_element_located(self.ERROR_MESSAGE))
            return error_element.text
        except:
            return None

# Test d'utilisation
class TestLogin:
    def setup_method(self):
        self.driver = WebDriverSetup.get_chrome_driver()
        self.login_page = LoginPage(self.driver)
    
    def test_invalid_login(self):
        self.login_page.navigate_to_login()
        self.login_page.enter_credentials("invalid@email.com", "wrongpassword")
        self.login_page.click_login()
        
        error_message = self.login_page.get_error_message()
        assert error_message is not None
        assert "Invalid credentials" in error_message
    
    def teardown_method(self):
        self.driver.quit()

Gestion des attentes et synchronisation

class WaitStrategies:
    def __init__(self, driver, timeout=10):
        self.driver = driver
        self.wait = WebDriverWait(driver, timeout)
    
    def wait_for_element_visible(self, locator):
        """Attendre qu'un élément soit visible"""
        return self.wait.until(EC.visibility_of_element_located(locator))
    
    def wait_for_element_clickable(self, locator):
        """Attendre qu'un élément soit cliquable"""
        return self.wait.until(EC.element_to_be_clickable(locator))
    
    def wait_for_text_in_element(self, locator, text):
        """Attendre qu'un texte apparaisse dans un élément"""
        return self.wait.until(EC.text_to_be_present_in_element(locator, text))
    
    def wait_for_ajax_complete(self):
        """Attendre que jQuery AJAX soit terminé"""
        return self.wait.until(
            lambda driver: driver.execute_script("return jQuery.active == 0")
        )

3. Playwright - L'outil moderne par Microsoft

Positionnement

Playwright représente l'évolution moderne des tests web avec une API intuitive, des performances élevées et des fonctionnalités avancées de debugging.

  • Avantages : API moderne, traces visuelles, auto-attentes, multi-contextes
  • Inconvénients : Écosystème récent, courbe d'apprentissage
  • Cas d'usage : Applications modernes, tests API + UI, debugging avancé

Configuration et API de base

# Installation
pip install playwright pytest-playwright
playwright install  # Installation des navigateurs

# Configuration de base
import pytest
from playwright.sync_api import sync_playwright, Page, BrowserContext

@pytest.fixture(scope="session")
def browser():
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False,  # Mode visible pour debugging
            slow_mo=50      # Ralentissement pour visualiser
        )
        yield browser
        browser.close()

@pytest.fixture
def context(browser):
    context = browser.new_context(
        viewport={'width': 1920, 'height': 1080},
        user_agent='Test Suite v1.0'
    )
    yield context
    context.close()

@pytest.fixture
def page(context):
    page = context.new_page()
    # Activation des traces pour debugging
    context.tracing.start(screenshots=True, snapshots=True)
    yield page
    context.tracing.stop(path="trace.zip")

Pattern Page Object avec Playwright

class DashboardPage:
    def __init__(self, page: Page):
        self.page = page
        
    # Locators avec Playwright (plus robustes)
    @property
    def user_menu(self):
        return self.page.locator('[data-testid="user-menu"]')
    
    @property
    def logout_button(self):
        return self.page.locator('text="Logout"')
    
    @property
    def project_cards(self):
        return self.page.locator('.project-card')
    
    def navigate(self):
        self.page.goto('/dashboard')
        # Auto-attente : Playwright attend automatiquement le chargement
        return self
    
    def logout(self):
        self.user_menu.click()
        # Les attentes sont automatiques avec Playwright
        self.logout_button.click()
        return self
    
    def get_project_count(self):
        # Attente automatique du chargement des éléments
        return self.project_cards.count()
    
    def search_project(self, project_name):
        search_input = self.page.locator('[placeholder="Search projects..."]')
        search_input.fill(project_name)
        search_input.press('Enter')
        return self

# Test avec assertions visuelles
class TestDashboard:
    def test_dashboard_layout(self, page):
        dashboard = DashboardPage(page)
        dashboard.navigate()
        
        # Screenshot de comparaison
        page.screenshot(path='dashboard.png')
        
        # Assertions multiples
        assert dashboard.get_project_count() > 0
        assert dashboard.user_menu.is_visible()
        
    def test_project_search(self, page):
        dashboard = DashboardPage(page)
        dashboard.navigate()
        dashboard.search_project("Test Project")
        
        # Attente et assertion sur le résultat filtré
        filtered_cards = page.locator('.project-card:visible')
        assert filtered_cards.count() == 1
        assert filtered_cards.first.locator('.project-title').text_content() == "Test Project"

Interception de requêtes et mocking

def test_api_mocking(page):
    # Mock d'une API
    page.route("**/api/projects", lambda route: route.fulfill(
        json=[
            {"id": 1, "name": "Mock Project", "status": "active"},
            {"id": 2, "name": "Test Project", "status": "completed"}
        ]
    ))
    
    # Interception de requêtes sortantes
    responses = []
    page.on("response", lambda response: responses.append(response))
    
    page.goto('/dashboard')
    
    # Vérification que l'API mockée a été appelée
    api_calls = [r for r in responses if "/api/projects" in r.url]
    assert len(api_calls) == 1

4. Cypress - L'expérience développeur optimale

Positionnement

Cypress révolutionne l'expérience de développement des tests avec son exécution dans le navigateur et ses outils de debugging en temps réel.

  • Avantages : DX exceptionnel, debugging en temps réel, API intuitive
  • Inconvénients : Limité à un seul navigateur par test, architecture contraignante
  • Cas d'usage : Développement TDD/BDD, prototypage rapide, formation équipes

Configuration et structure

# Installation
npm install cypress --save-dev
npx cypress open  # Interface graphique
npx cypress run   # Mode headless
// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'https://app.example.com',
    viewportWidth: 1920,
    viewportHeight: 1080,
    video: true,
    screenshotOnRunFailure: true,
    setupNodeEvents(on, config) {
      // Plugins et événements
    },
  },
  env: {
    apiUrl: 'https://api.example.com',
    testUser: 'test@example.com'
  }
})

Commands personnalisées et Page Objects

// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
  cy.session([email, password], () => {
    cy.visit('/login')
    cy.get('[data-cy="email"]').type(email)
    cy.get('[data-cy="password"]').type(password)
    cy.get('[data-cy="login-btn"]').click()
    cy.url().should('include', '/dashboard')
  })
})

Cypress.Commands.add('createProject', (projectData) => {
  cy.request({
    method: 'POST',
    url: `${Cypress.env('apiUrl')}/projects`,
    body: projectData,
    headers: {
      'Authorization': `Bearer ${window.localStorage.getItem('authToken')}`
    }
  })
})

// Page Object Pattern avec Cypress
class ProjectPage {
  constructor() {
    this.elements = {
      title: '[data-cy="project-title"]',
      description: '[data-cy="project-description"]',
      saveButton: '[data-cy="save-project"]',
      deleteButton: '[data-cy="delete-project"]',
      statusBadge: '[data-cy="project-status"]'
    }
  }

  visit(projectId) {
    cy.visit(`/projects/${projectId}`)
    return this
  }

  updateTitle(newTitle) {
    cy.get(this.elements.title).clear().type(newTitle)
    return this
  }

  save() {
    cy.get(this.elements.saveButton).click()
    cy.get('.success-toast').should('be.visible')
    return this
  }

  verifyStatus(expectedStatus) {
    cy.get(this.elements.statusBadge).should('contain.text', expectedStatus)
    return this
  }
}

Tests avec interception et assertions avancées

// cypress/e2e/project-management.cy.js
describe('Project Management', () => {
  const projectPage = new ProjectPage()

  beforeEach(() => {
    cy.login(Cypress.env('testUser'), 'password123')
  })

  it('should update project successfully', () => {
    // Interception de l'API
    cy.intercept('PUT', '/api/projects/*', {
      statusCode: 200,
      body: { message: 'Project updated successfully' }
    }).as('updateProject')

    // Création de données de test
    cy.createProject({
      name: 'Test Project',
      description: 'This is a test project'
    }).then((response) => {
      const projectId = response.body.id

      projectPage
        .visit(projectId)
        .updateTitle('Updated Project Name')
        .save()

      // Vérification de l'appel API
      cy.wait('@updateProject').then((interception) => {
        expect(interception.request.body).to.have.property('name', 'Updated Project Name')
      })

      // Assertions sur l'UI
      cy.get('[data-cy="project-title"]').should('have.value', 'Updated Project Name')
      projectPage.verifyStatus('Active')
    })
  })

  it('should handle network failure gracefully', () => {
    cy.intercept('PUT', '/api/projects/*', {
      statusCode: 500,
      body: { error: 'Internal server error' }
    }).as('failedUpdate')

    projectPage
      .visit(1)
      .updateTitle('This will fail')
      .save()

    cy.wait('@failedUpdate')
    cy.get('.error-toast').should('be.visible')
      .and('contain.text', 'Failed to update project')
  })
})

5. Comparaison et guide de choix

🔍 Matrice de décision

Critère Selenium Playwright Cypress
Courbe d'apprentissage Élevée Modérée Faible
Performance Correcte Excellente Bonne
Debugging Difficile Excellent Exceptionnel
Multi-navigateurs Excellent Excellent Limité
Écosystème Mature Émergent Riche

Recommandations architecturales

  • Selenium : Environnements legacy, tests de régression massifs, équipes Java/.NET
  • Playwright : Applications modernes, besoins de performance, tests API+UI
  • Cypress : Équipes front-end, développement TDD, prototypage rapide

6. Patterns et bonnes pratiques

Architecture de test robuste

tests/
├── pages/              # Page Objects
├── components/         # Composants réutilisables
├── fixtures/          # Données de test
├── utils/             # Utilitaires partagés
├── config/            # Configuration environnements
└── specs/             # Tests organisés par feature

Sélecteurs robustes

// ✅ Bonnes pratiques de sélection
'[data-testid="submit-button"]'     // Attributs dédiés aux tests
'[aria-label="Close dialog"]'       // Attributs d'accessibilité
'button:has-text("Save")'           // Sélection par contenu visible

// ❌ Sélecteurs fragiles à éviter
'#content > div:nth-child(3)'       // Position dans le DOM
'.btn-primary.large'                // Classes CSS de style
'input[name="field_1234"]'          // IDs générés dynamiquement

Gestion des données de test

# Pattern Factory pour les données de test
class UserFactory:
    @staticmethod
    def create_admin_user():
        return {
            "email": f"admin-{uuid.uuid4()}@test.com",
            "password": "SecurePass123!",
            "role": "admin",
            "permissions": ["read", "write", "delete"]
        }
    
    @staticmethod
    def create_basic_user():
        return {
            "email": f"user-{uuid.uuid4()}@test.com", 
            "password": "UserPass123!",
            "role": "user",
            "permissions": ["read"]
        }

# Cleanup automatique après tests
@pytest.fixture(autouse=True)
def cleanup_test_data():
    test_users = []
    yield test_users
    # Nettoyage des utilisateurs créés pendant le test
    for user in test_users:
        cleanup_user(user['id'])

Reporting et observabilité

# Intégration avec des outils de monitoring
class TestMetrics:
    def __init__(self):
        self.start_time = time.time()
        self.screenshots = []
        self.network_requests = []
    
    def capture_performance_metrics(self, page):
        # Métriques Core Web Vitals
        metrics = page.evaluate("""
            () => {
                return {
                    lcp: performance.getEntriesByType('largest-contentful-paint')[0]?.startTime,
                    fid: performance.getEntriesByType('first-input')[0]?.processingStart,
                    cls: performance.getEntriesByType('layout-shift')
                        .reduce((sum, entry) => sum + entry.value, 0)
                }
            }
        """)
        return metrics
    
    def generate_test_report(self):
        return {
            "duration": time.time() - self.start_time,
            "screenshots_count": len(self.screenshots),
            "network_calls": len(self.network_requests),
            "performance_metrics": self.performance_metrics
        }

7. Exercice pratique - Test E2E complet

Scénario : Application de gestion de tâches

Implémentez un test E2E complet pour une application Todo avec les fonctionnalités :

  • Authentification utilisateur
  • Création/modification/suppression de tâches
  • Filtrage par statut
  • Persistance des données
# Solution avec Playwright
class TodoApp:
    def __init__(self, page):
        self.page = page
        
    def login(self, email, password):
        self.page.goto('/login')
        self.page.fill('[data-testid="email"]', email)
        self.page.fill('[data-testid="password"]', password)
        self.page.click('[data-testid="login-submit"]')
        self.page.wait_for_url('**/dashboard')
        
    def create_task(self, title, description=""):
        self.page.click('[data-testid="add-task"]')
        self.page.fill('[data-testid="task-title"]', title)
        if description:
            self.page.fill('[data-testid="task-description"]', description)
        self.page.click('[data-testid="save-task"]')
        
    def mark_task_complete(self, task_title):
        task_row = self.page.locator(f'[data-task-title="{task_title}"]')
        task_row.locator('[data-testid="complete-checkbox"]').check()
        
    def filter_tasks(self, filter_type):
        self.page.click(f'[data-testid="filter-{filter_type}"]')
        
    def get_visible_tasks(self):
        return self.page.locator('[data-testid="task-item"]:visible').all()

def test_complete_todo_workflow(page):
    app = TodoApp(page)
    
    # 1. Login
    app.login("test@example.com", "password123")
    
    # 2. Créer plusieurs tâches
    tasks = ["Réviser les tests", "Implémenter feature X", "Code review"]
    for task in tasks:
        app.create_task(task)
    
    # 3. Marquer une tâche comme terminée
    app.mark_task_complete("Réviser les tests")
    
    # 4. Filtrer les tâches actives
    app.filter_tasks("active")
    visible_tasks = app.get_visible_tasks()
    assert len(visible_tasks) == 2
    
    # 5. Vérifier la persistance après refresh
    page.reload()
    app.filter_tasks("completed")
    completed_tasks = app.get_visible_tasks()
    assert len(completed_tasks) == 1
    
    # 6. Capture de l'état final pour documentation
    page.screenshot(path="final-state.png", full_page=True)
🎯 Prochaines étapes : Maîtrisez d'abord les fondations web de cette formation, puis choisissez l'outil de test adapté à votre contexte. L'expertise en tests automatisés se construit par la pratique régulière et l'analyse des échecs.