Comment automatiser les captures d'écran de sites web avec Playwright
Utilisez Playwright pour automatiser les captures d'écran de sites web avec JavaScript. Capturez une page, des pages entières, des vues mobiles et des lots d'URL avec des nouvelles tentatives.
14 min de lectureCapture de sites webPar Temposhot Team
Playwright est une bibliothèque d'automatisation de navigateur développée par Microsoft. Elle pilote Chromium, Firefox et WebKit depuis Node.js, Python, Java ou .NET. Cet article utilise le package Node.js pour écrire un script de capture d'écran, en commençant par une seule page et en terminant par un lot d'URL qui relance les échecs.
Quand Playwright est le bon outil
Un script vaut la mise en place lorsque les captures font partie d'un processus technique plus large :
- les captures s'exécutent en CI après chaque déploiement
- la liste d'URL provient d'un sitemap, d'une base de données ou d'une API
- les fichiers doivent être enregistrés dans une structure de dossiers fixe ou dans un bucket de stockage
- l'étape de capture s'inscrit à côté de tests, d'un scraping ou d'une génération de PDF
Si la personne qui a besoin des images n'écrit pas de code, ou si la liste change chaque semaine, un outil sans code est généralement plus rapide. Ce compromis est traité à la fin.
L'exemple concret de cet article
Les exemples convergent vers une même tâche : capturer les 200 pages listées dans le sitemap d'un site après chaque déploiement, en taille bureau et téléphone, puis signaler les pages en échec. Cette tâche réunit toutes les difficultés qui rendent les scripts de capture complexes : une longue liste, des pages lentes, des images en chargement différé, des bannières de cookies et des délais d'attente occasionnels.
Les chiffres de cette tâche sont des estimations. Les durées réelles dépendent du site, du réseau et de la machine.
Ce que coûte la maintenance du script
Le script est gratuit, mais il demande de la maintenance. Une refonte du site peut modifier la bannière de cookies, l'élément utilisé comme signal de disponibilité ou le conteneur de défilement, et chaque modification casse une partie du lot.
Node.js ou un autre langage
L'API de capture d'écran est presque identique dans les quatre liaisons de langage. page.screenshot(path=..., full_page=True) en Python fait la même chose que fullPage: true en Node.js. Choisissez le langage déjà utilisé par le reste de la chaîne, afin que l'étape de capture se trouve dans le même dépôt et le même job CI que le code qui l'entoure.
Prévoyez le temps de maintenance en conséquence. Un lot qui s'exécute après chaque déploiement rentabilise rapidement ce travail. Un lot qui ne tourne que deux fois par an échoue souvent dès la première tentative, car quelque chose a changé entre-temps.
Installer Playwright pour un script de capture
mkdir site-shots && cd site-shots
npm init -y
npm install -D playwright
npx playwright install chromium
La dernière commande télécharge une version de Chromium compatible avec la version de Playwright installée. Les scripts ci-dessous utilisent l'extension .mjs, ce qui permet d'utiliser await au niveau supérieur sans bundler ni configuration supplémentaire.
Figer la version de Playwright et celle du navigateur
Chaque version de Playwright attend des builds de navigateur précis. Après une mise à jour du package playwright, exécutez de nouveau npx playwright install chromium, sinon le script échoue avec un message indiquant que l'exécutable n'existe pas.
Versionnez package-lock.json pour que chaque machine installe la même version. Sur un runner CI Linux, npx playwright install --with-deps chromium installe aussi les bibliothèques système dont le navigateur a besoin.
La commande screenshot est utile pour vérifier une seule URL avant de l'ajouter au lot. Elle accepte aussi --device et --wait-for-selector, ce qui permet de tester une page lente depuis le terminal.
Capturer l'écran d'un site web
// one.mjs
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
await page.goto('https://example.com');
await page.screenshot({ path: 'example.png' });
await browser.close();
Exécutez-le avec node one.mjs. Le navigateur démarre en mode headless, c'est-à-dire qu'il rend la page sans ouvrir de fenêtre. L'entrée headless browser décrit ce modèle.
Sans autres options, le fichier est une viewport screenshot de 1920×1080 pixels exactement.
Options qui rendent les captures reproductibles
Deux exécutions sur la même page doivent produire la même image si rien n'a changé. Quelques options de capture suppriment les sources habituelles de bruit : animations capturées au milieu, curseur de texte clignotant et densité de pixels qui varie d'une machine à l'autre.
await page.screenshot({
path: 'example.jpg',
type: 'jpeg',
quality: 80,
animations: 'disabled',
caret: 'hide',
scale: 'css',
mask: [page.locator('.ad-slot'), page.locator('.live-chat')],
});
mask recouvre une zone au-dessus de chaque élément correspondant. C'est le moyen le plus simple d'éviter que les publicités rotatives, les horodatages en direct et les widgets de chat rendent chaque exécution différente.
Déboguer une capture vide ou incorrecte
Une capture vide, qui affiche une page d'erreur ou une vérification anti-bot, demande d'examiner ce que le navigateur a vu. Lancez avec headless: false et l'option slowMo pour suivre chaque étape, ou enregistrez une trace et ouvrez-la dans le visualiseur de traces.
La trace affiche une bande de vignettes de la page et les requêtes réseau de chaque action. Une page entièrement rendue dans la bande mais capturée vide signifie généralement que la capture a été lancée avant la fin d'un fondu d'apparition, ce que corrige animations: 'disabled'.
Capturer une page entière
Ajoutez fullPage: true pour capturer tout le document défilable.
await page.screenshot({ path: 'example-full.png', fullPage: true });
La largeur reste celle de la fenêtre d'affichage, et la hauteur s'adapte à la page. Les pages très longues produisent des images très hautes, que certaines visionneuses réduisent fortement. Les pages avec un en-tête fixe peuvent afficher l'en-tête une seule fois en haut, car Playwright capture le document en une seule passe au lieu d'assembler des vues défilées.
Pour capturer un seul élément, appelez screenshot sur un localisateur : page.locator('main').screenshot({ path: 'main.png' }).
Capturer une zone fixe
clip enregistre un rectangle de la page en pixels CSS, défini par x, y, width et height. Cette option convient aux pages dont seuls les 1 200 premiers pixels comptent, par exemple une page d'accueil visible avant le pli plus la première section.
await page.screenshot({ path: 'hero.png', clip: { x: 0, y: 0, width: 1920, height: 1200 } });
Une zone qui dépasse le bas d'une page courte est coupée au bord de la page. Vérifiez la hauteur de l'image lorsque la zone doit être identique sur toutes les pages.
Placer l'URL cible dans un script de lot
Gardez les URL dans un fichier texte afin que personne n'ait à modifier le code pour changer la liste.
// batch.mjs
import { readFile, mkdir } from 'node:fs/promises';
import { chromium } from 'playwright';
const urls = (await readFile('urls.txt', 'utf8'))
.split('\n').map((l) => l.trim()).filter(Boolean);
await mkdir('shots', { recursive: true });
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
for (const [i, url] of urls.entries()) {
const name = `shots/${String(i + 1).padStart(3, '0')}.png`;
await page.goto(url, { waitUntil: 'load', timeout: 30000 });
await page.screenshot({ path: name, fullPage: true });
console.log('saved', url, name);
}
await browser.close();
Les noms avec zéros de tête comme 001.png gardent les fichiers dans l'ordre de la liste dans le Finder.
Construire la liste à partir d'un sitemap
Pour la tâche d'exemple, la liste provient de sitemap.xml. Récupérez-le une fois au début de l'exécution et extrayez chaque entrée loc, afin que les nouvelles pages soient capturées sans modifier urls.txt.
const xml = await (await fetch('https://example.com/sitemap.xml')).text();
const urls = [...xml.matchAll(/<loc>(.*?)<\/loc>/g)].map((m) => m[1].trim());
Nommer les fichiers selon la page, pas seulement selon la position
Les noms d'index comme 001.png cassent lorsque le sitemap gagne une page, car chaque fichier suivant se décale d'un rang. Les noms construits à partir du chemin de l'URL restent stables d'une exécution à l'autre : /pricing/teams devient pricing-teams.png. Ne gardez l'index en préfixe que si l'ordre de la liste compte pour la relecture.
Deux URL peuvent donner le même nom, par exemple un chemin avec et sans barre oblique finale. Vérifiez les noms en double avant l'exécution et ajoutez l'index pour les départager.
Les grands sites répartissent leur sitemap entre un index qui pointe vers d'autres sitemaps. Suivez ces liens d'un niveau en profondeur, et filtrez les URL de fichiers comme les PDF, qui ne s'affichent pas comme des pages.
Ajouter la concurrence pour les lots plus importants
Une page à la fois est lente pour des centaines d'URL. Exécutez quelques pages en parallèle, chacune dans son propre objet page, et limitez leur nombre pour garder la mémoire sous contrôle.
const LIMIT = 4;
const queue = [...urls.entries()];
async function worker() {
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
while (queue.length) {
const [i, url] = queue.shift();
await page.goto(url);
await page.screenshot({ path: `shots/${i + 1}.png`, fullPage: true });
}
await page.close();
}
await Promise.all(Array.from({ length: LIMIT }, worker));
Quatre workers est un point de départ raisonnable sur un ordinateur portable. Augmentez la limite seulement après avoir vérifié l'usage mémoire, et gardez-la basse pour les sites que vous ne possédez pas afin de garder un débit de requêtes modéré.
Comment le nombre de workers modifie la durée
La durée d'exécution baisse presque proportionnellement au nombre de workers, jusqu'à ce que la machine ou le site devienne le goulot d'étranglement. Avec une estimation de 4 secondes par page, 200 pages prennent environ 13 minutes avec 1 worker, environ 3,5 minutes avec 4, et un peu moins avec 8.
Au-delà, les workers supplémentaires augmentent surtout la pression sur la mémoire et la charge du serveur. Si les captures commencent à expirer quand le nombre augmente, le site ou la machine est saturé, et il faut revenir à un nombre plus bas.
Limiter le débit de requêtes
Chaque chargement de page charge aussi des scripts, des images et des polices. Ainsi, 4 workers peuvent représenter plusieurs dizaines de requêtes par seconde vers un même serveur. Pour les sites que vous ne possédez pas, gardez 1 ou 2 workers et ajoutez une pause de 1 à 2 secondes entre les pages. Lancez les gros lots en dehors des heures de forte activité du site, et arrêtez-vous dès que les réponses deviennent des 429 Too Many Requests.
Pages ou contextes
Le worker ci-dessus partage un seul contexte de navigateur, donc toutes les pages partagent les cookies. Si une page enregistre un cookie de consentement, les pages suivantes ignorent la bannière, et les captures diffèrent selon l'ordre des workers. Donnez à chaque worker son propre contexte avec browser.newContext() lorsque chaque capture doit partir d'un état propre.
Capturer des vues mobiles ou tablette
Playwright fournit des descripteurs d'appareils qui règlent ensemble la fenêtre d'affichage, l'user agent, la prise en charge tactile et le ratio de pixels.
import { chromium, devices } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({ ...devices['iPhone 13'] });
const page = await context.newPage();
Utilisez un contexte par appareil pour que les cookies et le stockage restent séparés. Pour une tablette, choisissez une entrée iPad dans la même liste devices, ou définissez une fenêtre d'affichage simple comme 768×1024.
Ratio de pixels et taille de fichier
Les descripteurs de téléphone définissent deviceScaleFactor à 2 ou 3. Une fenêtre de téléphone de 390 pixels de large avec un facteur de 3 enregistre une image de 1 170 pixels de large, et une page longue peut atteindre plusieurs dizaines de milliers de pixels de haut. Cela représente neuf fois plus de pixels que la même page avec un facteur de 1.
Pour une relecture de la mise en page, scale: 'css' dans les options de capture conserve un pixel d'image par pixel CSS. Gardez le facteur d'échelle de l'appareil uniquement si les images doivent rester nettes sur l'écran d'un téléphone.
Vérifier que la mise en page mobile est chargée
Certains sites choisissent la mise en page à partir de l'user agent côté serveur, d'autres à partir de la largeur de la fenêtre en CSS. Un descripteur règle les deux, alors qu'un simple changement de viewport ne règle que la largeur. Si les captures mobiles montrent une mise en page de bureau écrasée, remplacez la simple fenêtre d'affichage par un descripteur d'appareil complet.
WebKit pour un rendu proche de Safari
Chromium est le choix par défaut dans la plupart des scripts, mais les mises en page iPhone sont rendues par WebKit sur les appareils réels. Installez-le avec npx playwright install webkit et lancez-le avec webkit.launch() lorsque le rendu propre à Safari compte.
Gérer le contenu chargé en différé
Beaucoup de pages ne chargent les images qu'au moment où elles entrent dans la zone visible. Une capture de page entière prise juste après le chargement peut montrer des cadres vides plus bas. Faites défiler la page une fois avant la capture.
await page.evaluate(async () => {
for (let y = 0; y < document.body.scrollHeight; y += window.innerHeight) {
window.scrollTo(0, y);
await new Promise((r) => setTimeout(r, 200));
}
window.scrollTo(0, 0);
});
L'entrée lazy loading explique pourquoi ce phénomène se produit.
Attendre que les images soient décodées
Le défilement lance les requêtes d'images, mais la capture peut encore se produire avant leur arrivée. Après le défilement, attendez que chaque image de la page signale qu'elle est terminée.
await page.waitForFunction(() =>
[...document.images].every((img) => img.complete && img.naturalWidth > 0)
);
Les URL d'images cassées ne se terminent jamais, donc cette attente peut atteindre son délai d'expiration sur une page avec un fichier manquant. Interceptez l'expiration, capturez quand même, puis signalez la page dans le rapport.
Choisir la bonne stratégie d'attente
L'option waitUntil détermine le moment où goto renvoie la main.
| Valeur | Retourne lorsque |
|---|---|
load | L'événement load se déclenche. C'est la valeur par défaut. |
domcontentloaded | Le HTML est analysé, avant la fin du chargement des images. |
networkidle | Aucune requête réseau pendant au moins 500 ms. |
La documentation de Playwright déconseille networkidle pour les tests, car les pages avec du polling ou des outils d'analyse ne deviennent parfois jamais silencieuses. Un modèle plus fiable consiste à attendre un élément qui marque la page comme prête, par exemple await page.locator('main img').first().waitFor().
Une attente fixe de 5 secondes sur 200 pages ajoute environ 17 minutes d'inactivité à un worker, et échoue quand même sur la page qui en nécessite 6. Une attente sur un localisateur se termine dès que la page est prête. await page.evaluate(() => document.fonts.ready) couvre le cas des polices.
Ajouter des nouvelles tentatives
Les réseaux échouent, et certaines pages expirent une fois avant de se charger correctement. Encapsulez la capture dans un petit helper de nouvelle tentative.
async function capture(page, url, path, tries = 3) {
for (let attempt = 1; attempt <= tries; attempt++) {
try {
await page.goto(url, { timeout: 30000 });
await page.screenshot({ path, fullPage: true });
return true;
} catch (error) {
console.warn(`attempt ${attempt} failed for ${url}: ${error.message}`);
}
}
return false;
}
Collectez les URL qui renvoient false et écrivez-les dans failed.txt pour pouvoir les relancer séparément.
Ne pas relancer ce qui ne changera pas
Les nouvelles tentatives aident contre les délais d'expiration et les connexions interrompues. Elles n'aident pas face à une page qui renvoie 404 ou 500, et trois tentatives sur 20 pages manquantes font perdre plusieurs minutes. page.goto renvoie un objet réponse : vérifiez response.status() et ne relancez pas lorsque le statut est 400 ou plus.
Inscrivez le statut dans le rapport à côté de l'URL. La liste des 404 issus d'un sitemap est utile en soi, car elle signale des entrées de sitemap obsolètes.
Récupérer d'une page qui a planté
Une page peut planter sur un site lourd, et chaque goto suivant sur cet objet page lève alors une exception. Dans le helper de nouvelle tentative, fermez la page après une erreur et ouvrez-en une nouvelle pour la tentative suivante. Le worker continue de tourner, et une URL défaillante ne fait plus échouer le reste de sa file.
Une structure de dossiers pratique
site-shots/
urls.txt
batch.mjs
shots/
2026-03-24/
desktop/001.png
mobile/001.png
failed.txt
Un dossier daté par exécution conserve les anciennes captures intactes. Un sous-dossier par appareil simplifie la relecture côte à côte.
Ce qu'une exécution CI doit envoyer
En CI, le runner est supprimé à la fin du job, donc les captures doivent être envoyées comme artefacts de build. Envoyez le dossier daté, la liste des échecs et un petit rapport JSON contenant, pour chaque page, l'URL, le statut, le nom du fichier et la durée.
Comparer une exécution avec la précédente
Deux dossiers datés ne servent à rien si personne n'examine les différences. Pour une poignée de pages, ouvrez les fichiers correspondants dans Compare Images, qui propose un curseur et une vue des différences de pixels. Pour des centaines de pages, l'assertion toHaveScreenshot de Playwright Test compare chaque capture à une référence enregistrée et échoue si les différences dépassent un seuil.
Les polices sont la source de différence la plus courante entre une exécution sur ordinateur portable et une exécution CI. Un runner Linux sans les polices de secours du site affiche le texte avec des largeurs différentes, donc les sauts de ligne et les hauteurs de page changent.
Erreurs courantes
- Oublier
await. Le script se termine avant l'écriture du fichier. - Réutiliser une page après un plantage. Ouvrez une nouvelle page lorsque
gotolève une erreur de type target-closed. - Compter sur
networkidlepartout. Certaines pages ne l'atteignent jamais, et chaque ligne expire. - Capturer les bannières de cookies. Cliquez sur le bouton de consentement avec un localisateur, ou masquez-le avec
page.addStyleTag. - Ignorer les conditions d'utilisation du site. Vérifiez les conditions d'utilisation et les règles de robots.txt des sites que vous ne possédez pas, et gardez un débit faible.
Fermer une bannière de cookies de façon fiable
Les bannières varient d'un site à l'autre, donc un seul sélecteur fonctionne rarement pour toute une liste. Essayez une courte liste de boutons de consentement connus avec un délai de 2 secondes pour chacun, puis masquez le conteneur de la bannière en CSS en dernier recours.
const consent = page.getByRole('button', { name: /accept|agree|reject all/i });
if (await consent.first().isVisible({ timeout: 2000 }).catch(() => false)) {
await consent.first().click();
}
Cliquer sur un bouton de consentement modifie les cookies pour le reste du contexte. Avec un contexte par worker, cela ne pose pas de problème ; avec un contexte partagé, c'est une raison supplémentaire pour que les captures diffèrent selon l'ordre.
Alternatives à un script de capture Playwright
- Puppeteer propose une API similaire pour Chrome et Firefox. Voir Comment automatiser les captures d'écran de sites web avec Puppeteer.
- Playwright Test compare les captures à des références. Voir le guide de test de captures d'écran avec Playwright.
- Les API de capture d'écran hébergées exécutent le navigateur à votre place et renvoient les images via HTTP.
- Temposhot Website Capture exécute localement sur un Mac une liste d'URL, sans code. Les libellés de l'application sont en anglais : Desktop 1920×1080, Tablet 768×1024 ou Mobile 390×844, Viewport ou Full page, jusqu'à 10 navigateurs en même temps, et Retry par ligne.
Options de script et équivalents sans code
La plupart des choix de cet article ont un réglage direct dans Website Capture. La différence tient à la personne qui fait le choix : un développeur dans le code, ou toute personne capable de coller une liste. Website Capture ne propose pas de planificateur ni de connexion, donc les exécutions CI et les pages protégées par un identifiant restent dans le script.
FAQ
Playwright a-t-il besoin d'une fenêtre de navigateur visible ?
Non. Les navigateurs démarrent en mode headless par défaut. Passez headless: false à chromium.launch lorsque vous voulez observer la page pendant le débogage.
Playwright peut-il enregistrer en JPEG au lieu de PNG ?
Oui. Définissez type: 'jpeg' et une valeur quality de 0 à 100 dans les options de capture, ou utilisez un nom de fichier .jpg.
Pourquoi ma capture de page entière est-elle coupée ?
Certaines pages défilent dans un élément interne au lieu du document. Capturez cet élément avec une capture par localisateur, ou modifiez sa hauteur CSS avant la capture.
Playwright peut-il capturer des pages protégées par une connexion ?
Oui. Connectez-vous une fois, enregistrez la session avec context.storageState({ path: 'auth.json' }), puis créez les contextes suivants avec storageState: 'auth.json'. Gardez ce fichier hors du contrôle de version, car il contient les cookies de session.
Comment empêcher les publicités et les horodatages de changer à chaque capture ?
Passez leurs localisateurs dans l'option mask. Playwright recouvre chacun d'eux d'un bloc plein, ce qui permet de comparer les exécutions sans fausses différences.
Pourquoi les captures CI diffèrent-elles de mon ordinateur portable ?
Les polices, le ratio de pixels et la locale diffèrent généralement. Installez les mêmes polices sur le runner, définissez scale: 'css', et fixez locale et timezoneId sur le contexte afin que les dates et les formats de nombres correspondent.
Références
- Documentation Playwright sur les captures d'écran
- Émulation et appareils Playwright
- Installation de Playwright
Besoin d'une automatisation de captures d'écran de sites sans code ?
Temposhot Website Capture prend une liste d'URL collée et capture chaque page sur votre Mac, sans script à maintenir. Voir automatisation des captures d'écran de sites web ou commencez par le guide de démarrage rapide de Website Capture.
playwrightwebsite-captureautomationjavascript
Termes utilisés ici : full page screenshot, playwright, puppeteer, screenshot api, screenshot automation, viewport screenshot
Transformez vos captures d'écran répétitives en résultats prêts à l'emploi
Temposhot capture l'écran à intervalles réguliers ou à chaque clic et exporte le résultat au format PDF, image, ZIP ou MP4.
Articles connexes
- Comment capturer en lot des captures d'écran de sites web sur MacCapturez en lot des sites web sur Mac avec Temposhot. Collez une liste d'URL, choisissez les préréglages bureau, tablette ou mobile, lancez une capture en parallèle et exportez pour des audits ou des rapports.
- Comment automatiser les captures d'écran de sites web avec PuppeteerUtilisez Puppeteer pour automatiser les captures d'écran de sites web en JavaScript. Capturez une page, des pages entières, des lots d'URL et des vues mobiles avec un script réutilisable.
- Comment automatiser les captures d'écran de sites web sans codeChoisissez une méthode sans code pour automatiser les captures d'écran de sites web : liste d'URL, plateforme d'automatisation avec API, outil de surveillance ou extension de navigateur.
- Meilleures alternatives à Xnapper pour Mac en 2026Comparez les alternatives à Xnapper pour Mac : captures répétées, capture et enregistrement de défilement, arrière-plans gratuits, guides multiplateformes et l'application intégrée.