Créer un outil d’automatisation en JavaScript modulaire de zéro
Apprenez à combiner Playwright, Cheerio, SQLite et Commander pour créer un moteur de workflow Node.js réutilisable, qui évolue d’un simple script en un produit d’automatisation vendable.
1. Un problème récurrent est devenu le point de départ
À un moment donné, vous vous rendez compte que vous effectuez sans cesse le même type de tâche.
Ouvrez une page.
Examinez-la à la recherche de quelque chose d’utile.
Sélectionnez les éléments pertinents.
Stockez-les quelque part pour plus tard.
Passez à la page suivante.
Reprenez le cycle.
Aucune de ces étapes n’est difficile en soi, mais leur répétition incessante est absurde.
Au lieu de choisir un autre framework JavaScript juste pour créer un autre tableau de bord, il est plus utile de développer quelque chose que l’on peut réellement utiliser : un outil d’automatisation et de collecte de données en JavaScript.
L’idée est simple :
Donnez une tâche au programme → laissez JavaScript s’occuper de la partie répétitive → obtenez des résultats structurés.
Une version initiale d’un tel outil n’a besoin que de quelques technologies :
- Node.js
- Playwright
- Cheerio
- SQLite
- Commander
Cette combinaison suffit pour transformer un petit script en quelque chose qui commence à ressembler à un véritable produit.
2. Playwright en tant que premier élément de construction
L’objectif était de permettre à JavaScript de piloter un vrai navigateur.
Playwright rend cela bien plus simple que prévu.
const { chromium } = require("playwright");
async function visitWebsite(url) {
const browser = await chromium.launch({
headless: false
});
const page = await browser.newPage();
await page.goto(url, {
waitUntil: "domcontentloaded"
});
console.log(
"Page title:",
await page.title()
);
console.log(
"Current URL:",
page.url()
);
await browser.close();
}
visitWebsite(
"https://example.com"
);
La première fois que vous exécutez quelque chose de ce genre, cela semble presque trop facile.
JavaScript ouvre un navigateur.
JavaScript navigue vers une page.
JavaScript lit son contenu.
JavaScript ferme le navigateur.
Cela seul vous fournit une base sur laquelle vous pouvez développer des tests de navigateur, un suivi, des workflows répétitifs et une automatisation générale.
3. Passer des coordonnées aux éléments
Une chose à éviter dès le début, c’est l’automatisation fragile.
En indiquant à votre programme quelque chose comme :
Cliquez à cet endroit précis.
cela signifie que l’automatisation peut cesser de fonctionner dès qu’il y a le moindre changement dans la disposition de l’écran.
Une approche plus efficace consiste à écrire du code qui décrit ce avec quoi l’utilisateur interagit réellement, plutôt que l’emplacement exact des éléments à l’écran.
async function searchPage(page, query) {
await page
.getByRole("textbox")
.fill(query);
await page
.getByRole("button", {
name: "Search"
})
.click();
await page.waitForLoadState(
"domcontentloaded"
);
}
Cela est bien plus facile à maintenir avec le temps.
Le code ne dit pas :
Cliquez sur l’élément situé aux coordonnées 742, 381.
Il dit plutôt :
Trouvez la zone de texte et le bouton de recherche.
C’est un petit choix de conception, mais il rend considérablement moins complexe l’automatisation des navigateurs par la suite.
4. Extraction des données depuis la page
Dès que le navigateur peut naviguer seul sur une page, l’étape suivante consiste à lui faire collecter des informations.
Prenons par exemple une page remplie de fiches produits.
async function extractProducts(page) {
return page
.locator(".product-card")
.evaluateAll(cards => {
return cards.map(card => {
const name =
card
.querySelector(".product-name")
?.textContent
?.trim();
const price =
card
.querySelector(".price")
?.textContent
?.trim();
return {
name,
price
};
});
});
}
À ce stade, le navigateur ne se contente plus de visiter des pages.
Il convertit le contenu de la page en objets JavaScript.
Cela permet d’utiliser les données pour des traitements ultérieurs.
const products =
await extractProducts(page);
console.log(
JSON.stringify(
products,
null,
2
)
);
Lorsque les informations possèdent une structure réelle, on peut les stocker, les comparer, effectuer des analyses dessus ou les transmettre à une autre partie de l’application.
5. Utilisation de Cheerio pour le parsing HTML
Playwright se révèle particulièrement utile lorsque l’on a vraiment besoin d’un navigateur en exécution.
Mais souvent, on dispose déjà de l’HTML et il n’est pas nécessaire d’exécuter Chromium du tout.
C’est là que Cheerio trouve son utilité.
const cheerio = require("cheerio");
function parseProducts(html) {
const $ = cheerio.load(html);
const products = [];
$(".product-card").each(
(_, element) => {
const name = $(element)
.find(".product-name")
.text()
.trim();
const price = $(element)
.find(".price")
.text()
.trim();
products.push({
name,
price
});
}
);
return products;
}
Avoir ces deux outils à disposition représente un réel avantage.
Playwright gère les interactions avec le navigateur.
Cheerio s’occupe de l’analyse légère d’HTML.
Ainsi, une instance complète de navigateur n’est utilisée que lorsque c’est vraiment nécessaire, tandis qu’un analyseur simple s’occupe du reste.
6. Fournir de la mémoire à l’automatisation avec SQLite
Le stockage était le prochain défi à résoudre.
Si un script collecte des données aujourd’hui, où se trouvent ces données demain ?
La solution a été d’intégrer SQLite.
const sqlite3 = require("sqlite3").verbose();
const db = new sqlite3.Database(
"automation.db"
);
db.run(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price TEXT,
source TEXT,
created_at DATETIME
DEFAULT CURRENT_TIMESTAMP
)
`);
Par la suite, une fonction dédiée s’est chargée de sauvegarder chaque résultat.
function saveProduct(product, source) {
return new Promise(
(resolve, reject) => {
db.run(
`
INSERT INTO products
(name, price, source)
VALUES (?, ?, ?)
`,
[
product.name,
product.price,
source
],
error => {
if (error) {
reject(error);
return;
}
resolve();
}
);
}
);
}
Cette ajoutation a modifié de manière significative la structure du projet.
Avec une base de données en place, les archives historiques peuvent s’accumuler avec le temps. Cela a permis de répondre à des questions pratiques telles que :
- Qu’est-ce qui a changé ?
- Qu’est-ce qui est apparu récemment ?
- Qu’est-ce qui a disparu ?
L’automatisation devient bien plus précieuse lorsqu’elle peut se souvenir de ce qui s’est passé auparavant.
7. Assemblage d’un moteur de flux de travail réutilisable
À ce stade, le projet comprenait plusieurs éléments indépendants : le contrôle du navigateur, l’analyse HTML et la persistance dans une base de données. Ce qui lui manquait, c’était une structure — il y avait un risque réel de se retrouver avec des fonctions dispersées partout au lieu d’un système cohérent.
Pour y remédier, une classe de flux de travail a rassemblé tout cela.
class AutomationWorkflow {
constructor(browser) {
this.browser = browser;
this.page = null;
}
async start() {
this.page =
await this.browser.newPage();
}
async visit(url) {
await this.page.goto(url, {
waitUntil: "domcontentloaded"
});
}
async search(query) {
await this.page
.getByRole("textbox")
.fill(query);
await this.page
.getByRole("button", {
name: "Search"
})
.click();
}
async getTitle() {
return this.page.title();
}
async close() {
await this.page.close();
}
}
Avec cette classe en place, il devient simple de suivre une tâche complète du début à la fin.
const browser =
await chromium.launch({
headless: false
});
const workflow =
new AutomationWorkflow(
browser
);
await workflow.start();
await workflow.visit(
"https://example.com"
);
await workflow.search(
"JavaScript automation"
);
console.log(
await workflow.getTitle()
);
await workflow.close();
await browser.close();
C’est ici que la conception orientée objet a commencé à porter ses fruits. L’instance de classe modélise le flux de travail lui-même, et ses méthodes représentent les opérations individuelles. Le reste du codebase n’a pas besoin de savoir comment chaque étape est mise en œuvre en interne.
8. Gestion des pannes avec une logique de tentative
Les scripts d’automatisation ont tendance à fonctionner parfaitement neuf fois avant de échouer à la dixième. Le réseau ralentit, une page n’est pas entièrement chargée, un serveur rencontre des problèmes, ou un élément met plus de temps que prévu à s’afficher.
Pour y remédier, un outil générique de tentative a été introduit.
async function retry(
operation,
attempts = 3,
delay = 2000
) {
let lastError;
for (
let attempt = 1;
attempt <= attempts;
attempt++
) {
try {
return await operation();
} catch (error) {
lastError = error;
console.log(
`Attempt ${attempt} failed.`
);
if (
attempt < attempts
) {
await new Promise(
resolve =>
setTimeout(
resolve,
delay
)
);
}
}
}
throw lastError;
}
Cet outil a permis d’encadrer les étapes critiques avec des tentatives automatiques.
await retry(
async () => {
await page.goto(
"https://example.com",
{
waitUntil:
"domcontentloaded"
}
);
},
3,
1500
);
La leçon était simple : une automatisation de niveau production doit prévoir l’échec comme un événement normal, et non comme une exception. Les tutoriels partent généralement du principe qu’un réseau est parfaitement coopératif. Les systèmes réels ne peuvent se permettre de faire cette supposition.
9. L’envelopper dans une interface en ligne de commande
À un certain moment, ouvrir le fichier source à chaque fois uniquement pour modifier une URL est devenu fastidieux.
L’objectif a alors été de faire en sorte que l’outil se comporte comme un véritable outil en ligne de commande.
Commander a rendu cela facile à réaliser.
const { Command } = require("commander");
const program = new Command();
program
.name("webpilot")
.description(
"JavaScript automation toolkit"
)
.version("1.0.0");
program
.command("visit")
.description(
"Open a webpage"
)
.argument("<url>")
.action(async url => {
const browser =
await chromium.launch({
headless: false
});
const page =
await browser.newPage();
await page.goto(url);
console.log(
await page.title()
);
await browser.close();
});
program.parseAsync();
Avec cela en place, l’outil pouvait être lancé directement depuis une terminal.
node webpilot.js visit https://example.com
Cela semble être un ajustement mineur, mais cela change fondamentalement la manière dont on interagit avec le logiciel.
Au lieu d’éditer le programme chaque fois qu’on a besoin de quelque chose,
on se contente de le lancer.
10. Considérer l’IA comme l’interface utilisateur
Cela soulève une nouvelle question :
Pourquoi l’utilisateur devrait-il mémoriser la syntaxe exacte des commandes ?
Au lieu de taper quelque chose comme :
node webpilot.js screenshot https://example.com
une personne pourrait simplement décrire son intention en langage courant :
"Prendre une capture d’écran de la page d’accueil."
Une couche d’IA pourrait traduire cette phrase en un objet de tâche structuré.
const task = {
action: "screenshot",
url: "https://example.com",
output: "homepage.png"
};
Cependant, l’IA ne recevra jamais la permission d’exécuter directement du JavaScript arbitraire.
Chaque action demandée passera d’abord par une étape de validation.
const allowedActions = new Set([
"visit",
"search",
"screenshot",
"download"
]);
function validateTask(task) {
if (
!allowedActions.has(
task.action
)
) {
throw new Error(
"Unsupported action."
);
}
if (
task.url &&
!task.url.startsWith("https://")
) {
throw new Error(
"Invalid URL."
);
}
return true;
}
Cela permet une séparation claire des responsabilités :
L’IA interprète ce que l’utilisateur souhaite.
La couche JavaScript décide de ce qui est réellement autorisé.
Playwright ne met en œuvre que l’action approuvée.
Ce type de conception en couches représente une approche bien plus sûre que de confier à un modèle d’IA un contrôle direct et illimité sur la machine.
11. Repenser le projet comme un produit vendable
À ce stade, l’attention s’est déplacée des bibliothèques sous-jacentes vers le client réel.
La présentation ne devait jamais être :
« Une application d’automatisation de navigateur développée avec Playwright et JavaScript. »
Nul ne cherche spécifiquement Playwright — ils cherchent un résultat.
Ainsi, l’offre est devenue le résultat lui-même. Quelques exemples :
Surveillance des sites web
Les entreprises peuvent suivre leurs propres sites web et être averties lorsque des pages clés changent ou tombent en panne.
QA automatisé
Les équipes d’ingénierie peuvent effectuer des vérifications de navigateur cohérentes et reproductibles sur leurs propres applications.
Automatisation des workflows internes
Les organisations peuvent automatiser les tâches répétitives basées sur le navigateur au sein des outils auxquels elles ont déjà accès.
Automatisation de la génération de rapports
Une tâche planifiée peut collecter automatiquement les données approuvées, les enregistrer et les compiler en un rapport.
Automatisation pour les agences
Une agence peut concevoir des pipelines d’automatisation sur mesure pour ses clients et facturer pour la mise en place ainsi que le soutien continu.
Les tarifs peuvent prendre plusieurs formes :
- Un frais de mise en place forfaitaire unique
- Des frais de maintenance mensuels récurrents
- Un tarif par workflow individuel
- Une licence par équipe
- Des travaux d’intégration sur mesure
- Un accès hébergé par abonnement
Le point clé consiste à ancrer l’offre sur un problème concret et spécifique, plutôt que sur l’ensemble des technologies utilisées.
12. De petits scripts peuvent devenir de véritables produits
À la fin, l’architecture globale du système ressemblait à peu près à ceci :
User
│
▼
CLI / AI Input
│
▼
Task Validator
│
▼
Workflow Engine
│
┌────────┼────────┐
▼ ▼ ▼
Playwright Cheerio SQLite
│ │ │
└────────┼────────┘
▼
Result Data
│
▼
Report / API
C’est la partie qui mérite d’être approfondie.
Le problème de fond n’avait jamais vraiment trait à l’automatisation des navigateurs en soi.
Il s’agissait plutôt de éliminer le travail manuel répétitif.
Les bibliothèques — Playwright, Cheerio, SQLite, Commander — n’étaient que des mécanismes permettant de transformer cette répétition en logiciel fonctionnel.
Cette mentalité influence aujourd’hui la manière dont on aborde de nouveaux projets JavaScript.
Dès qu’une séquence d’étapes manuelles commence à se répéter pour la vingtième fois, l’instinct n’est pas :
"Il est temps de passer à un nouveau framework."
C’est plutôt :
« Ce flux de travail pourrait-il être transformé en fonction ? »
Si la réponse est oui, c’est là le point de départ d’un projet d’automatisation.
Lorsque cette automatisation permet à quelqu’un d’économiser suffisamment de temps et d’efforts, elle peut se développer en quelque chose ayant de la valeur à vendre.
Lectures complémentaires
- Conception d’une API Node.js en niveaux : des controlleurs lourds à une architecture nette — Apprenez comment refactorer une API Node.js en couches de controlleurs, de services et d’accès aux données afin de résoudre les logiques métier complexes, les erreurs incohérentes et les difficultés de mise à l’échelle.