Captures fiables avec un navigateur headless
Méthode pratique pour des captures headless fiables avec viewport du profil, état prêt, capture pleine page native, vérification du bas et mémoire des conteneurs.
BotBrowser Team
Vous préférez la doc produit maintenue ?
Cet article a une page équivalente dans le centre de documentation. Utilisez les docs pour le flux canonique, les flags à jour et la référence durable.
BotBrowser permet de répéter des captures dans des contextes contrôlés, mais ne certifie pas l'égalité visuelle entre hôtes, écrans, polices ou états applicatifs. Utilisez ce flux pour tester une route maîtrisée et noter la limite réellement vérifiée.
Résumé
Conservez le viewport du profil, attendez les marqueurs visibles prêt et fin, utilisez la capture pleine page native, vérifiez la partie basse et prévoyez la mémoire du conteneur pour l'image approuvée la plus haute. BotBrowser offre une capture répétable, pas une équivalence visuelle universelle.
Sommaire
- Commencer par le viewport du profil
- Définir l'état prêt avec des signaux de la page
- Capturer le document sans étirer le viewport
- Vérifier la partie basse des images longues
- Choisir le format selon l'usage
Commencer par le viewport du profil
Une capture n'est utile que si elle représente la page que le navigateur devait rendre. Dans une session liée à un profil, le viewport est le premier élément du contrat de rendu. Il contrôle les retours à la ligne, les changements responsive, la navigation fixe, la taille des images et la quantité de contenu visible avant le défilement. Ce n'est pas une valeur provisoire que le script peut modifier pour faciliter la capture.
Une erreur fréquente consiste à laisser la bibliothèque d'automatisation choisir un viewport pratique. Le contexte peut être plus étroit que le profil choisi et la page se réorganise avant la création de l'image. Une autre erreur consiste à donner au viewport la hauteur du document pour obtenir une image pleine page. Cela change la mise en page responsive et le résultat ne représente plus la session du navigateur.
Lorsque le profil doit fournir les dimensions, ne configurez pas le viewport du
contexte. Avec Puppeteer, utilisez defaultViewport: null. Avec Playwright, créez
le contexte sans remplacer viewport. Si le produit exige une autre dimension,
inscrivez ce choix dans le profil et dans la spécification de capture, puis
conservez-le pour les exécutions comparables.
const browser = await chromium.launch({
executablePath: process.env.BROWSER_BINARY,
args: [`--bot-profile=${process.env.BROWSER_PROFILE}`],
});
const context = await browser.newContext();
const page = await context.newPage();
L'exemple ne contient volontairement aucun réglage de viewport. Cette absence est la partie importante. N'utilisez pas une fonction de resize pour faire coïncider le viewport avec la hauteur du document. Un document long doit rester long dans le viewport du profil.
Ce que la capture prouve et ce que la page annonce
Une capture est une preuve sur des pixels, pas une affirmation sur les valeurs que la page a calculées. Gardez les deux côte à côte pour que chaque question de revue arrive au bon artefact.
| Signal | Ce que la capture montre | Ce que la même page annonce | Pourquoi l'écart compte |
|---|---|---|---|
| Largeur du viewport | La largeur à laquelle la mise en page a été dessinée | window.innerWidth et window.visualViewport.width | Une capture prise après un resize paraît encore valable, alors que la largeur annoncée révèle quel point de rupture s'est appliqué. |
| Taille de l'écran | Rien sur l'écran émulé | screen.width et screen.height | L'écran du profil peut différer du viewport du contexte, et seules les valeurs annoncées l'exposent. |
| Facteur de pixels | Les dimensions en pixels de la sortie | window.devicePixelRatio | Une image de 3840 de large peut venir d'un viewport de 1920 au facteur 2 ou d'un viewport de 3840 au facteur 1. |
| Famille de polices chargée | Les formes des glyphes, après coup | document.fonts.status et document.fonts.check() | Les glyphes de remplacement se ressemblent sur une petite image, mais l'état annoncé nomme la famille utilisée. |
| État de l'animation | Un instant figé | element.getAnimations() et la durée de transition | Deux images de la même page peuvent différer seulement parce que la capture est tombée sur une autre image de rendu. |
| Géométrie de l'élément | Les pixels où un élément a été peint | element.getBoundingClientRect() | Un pied de page rogné paraît correct en haut de l'image ; le rectangle montre la position réelle. |
Utilisez le tableau des attributs pour les questions qu'une image ne tranche pas. Si quelqu'un demande quel point de rupture, quelle police ou quelle image de rendu, la réponse vient d'une valeur calculée lue dans la même session, pas d'un second regard sur l'image.
Définir l'état prêt avec des signaux de la page
La fin de la navigation ne signifie pas que l'image est prête. La page peut avoir terminé sa navigation initiale alors que l'application place encore des cartes, charge un graphique, choisit la langue ou remplace une police de secours. Une capture prise entre ces étapes est une image valide d'un état que le lecteur ne devait pas voir.
Définissez un petit contrat de disponibilité pour chaque route de capture. Un marqueur créé par la page est souvent plus fiable qu'une attente fixe, car la page peut le créer lorsque le contenu requis est présent. Il peut s'agir d'un élément avec un attribut stable, d'un état visible ou d'un composant de route qui apparaît après la dernière modification de mise en page. Le contrat doit décrire le contenu visible et ne pas dépendre d'un événement privé du navigateur.
document.readyState passe à 'complete' une fois l'événement load déclenché,
ce qui ne dit rien de la dernière passe de mise en page. Une zone qui pose
aria-busy="true" pendant sa mise à jour puis retire l'attribut à la fin offre
un repère standard, et document.fonts.status indique 'loading' ou 'loaded'
pour le jeu de polices.
Un contrat utile couvre généralement les points suivants :
- Le conteneur principal existe et est visible.
- Les données de la vue demandée sont rendues.
- Les changements de mise en page qui suivent l'arrivée des données sont terminés.
- Les polices des titres, libellés et textes sont prêtes.
- Les images de la zone demandée sont chargées ou dans un état de remplacement voulu.
- Une capture pleine page possède un marqueur de fin clair.
Le marqueur de fin est important pour les pages qui assemblent leur contenu par étapes. La présence de la première carte ne dit rien sur la moitié basse d'un rapport. Attendre le marqueur de fin de la page donne une limite stable. Si la route n'a pas de fin naturelle, définissez une limite de capture explicite au lieu de considérer un document qui grandit sans fin comme terminé.
Un ordre d'attente concret
Le modèle suivant combine un marqueur de route, l'état des polices et celui des images. Il ne dort pas pendant un intervalle arbitraire. Adaptez les sélecteurs à chaque page et faites des marqueurs une partie du contrat de la route, plutôt qu'un sélecteur générique partagé partout.
async function waitForCaptureReady(page) {
await page.waitForLoadState('domcontentloaded');
await page.locator('[data-capture-ready]').waitFor({ state: 'visible' });
await page.locator('[data-capture-end]').waitFor({ state: 'visible' });
await page.evaluate(async () => {
await document.fonts.ready;
await Promise.all(
[...document.images].map(image =>
image.complete
? undefined
: new Promise(resolve => {
image.addEventListener('load', resolve, { once: true });
image.addEventListener('error', resolve, { once: true });
})
)
);
});
}
La branche des images traite une vignette optionnelle indisponible comme un remplacement terminé, afin qu'une ressource secondaire ne bloque pas le travail. Si chaque image est obligatoire, faites apparaître le marqueur seulement après les ressources requises et enregistrez clairement l'absence d'une ressource.
L'absence d'activité réseau peut servir de signal complémentaire, mais ne doit pas être le seul. Les mises à jour en direct, les connexions persistantes et les requêtes périodiques peuvent garder une page occupée alors que son contenu visible est prêt. À l'inverse, un rendu côté client peut finir sans nouvelle requête. Le contrat de la page doit exprimer l'état que le lecteur doit voir.
Évitez une pause fixe après la navigation. Elle fait attendre une page rapide et peut encore capturer trop tôt une page lente. Elle masque aussi la raison pour laquelle la page n'était pas prête. Un marqueur stable fournit un point vérifiable lorsque la route évolue.
Capturer le document sans étirer le viewport
Pour une image du viewport, utilisez l'option de capture normale. Pour une image du document, utilisez l'option native pleine page de la bibliothèque d'automatisation. Elle conserve le viewport du profil et inclut le contenu situé sous la ligne de flottaison.
await waitForCaptureReady(page);
await page.screenshot({
path: outputPath,
fullPage: true,
type: 'png',
});
Ne donnez pas au viewport la hauteur du document avant cet appel. Ce raccourci modifie la mise en page responsive, peut empêcher le défilement qui déclenche le chargement différé et produit une image qui ne représente plus le profil choisi. Il peut aussi créer une surface plus grande que les capacités du chemin graphique ou de la mémoire du conteneur.
Une séquence de capture déterministe
L'ordre compte plus que n'importe quelle attente isolée. Exécutez ces étapes dans l'ordre, et chacune répond à une question que la précédente laisse ouverte.
- Lancez avec le profil et sans surcharge de viewport, pour que
defaultViewport: nullsous Puppeteer ou un contexte intact sous Playwright annonce les dimensions du profil. - Naviguez avec
waitUntil: 'domcontentloaded'puis arrêtez-vous. L'événement load dit que l'analyseur a terminé, pas que la mise en page est définitive. - Attendez le marqueur de préparation de la page (
[data-capture-ready]) pour que la mise en page pilotée par les données ait eu lieu. Sans lui, une exécution rapide capture un conteneur vide. - Attendez le marqueur de fin de la page (
[data-capture-end]) pour que le contenu progressif soit complet. Sans lui, un rapport long perd sa moitié basse. - Attendez
document.fonts.readypour que le texte soit dessiné avec le paquet de polices du profil et non avec un remplacement dont les métriques ne correspondront jamais. - Résolvez chaque
imgde la zone de capture en load ou error, pour qu'une image tardive ne décale pas la mise en page entre deux exécutions. - Préparez les sections différées par un défilement normal, revenez en haut et refaites les étapes 3 et 4. Ce passage demande le contenu à la page ; l'image finale reste produite par l'option de capture.
- Capturez avec
fullPage: trueettype: 'png'depuis le viewport inchangé, pour que la limite de l'image corresponde à la mise en page mesurée. - Notez
window.innerWidth,window.devicePixelRatioet l'état de polices résolu à côté du fichier, pour qu'une comparaison ultérieure nomme le réglage qui a changé.
Écrivez le fichier sous un nom temporaire dans le volume de sortie et déplacez-le en place une fois l'écriture fermée, pour qu'un travail annulé ne remplace pas l'image approuvée précédente par une image partielle.
Si la page contient du contenu différé, préparez-le avec un défilement normal avant la capture pleine page native. Le défilement demande à la page de charger la zone voulue. Il ne remplace pas la capture pleine page. Revenez à la position supérieure, confirmez le contrat de disponibilité, puis laissez l'option native recueillir le document.
Le défilement infini exige une limite explicite. Décidez si l'image s'arrête à l'ensemble déjà chargé, à une section nommée ou à un état final de la route. Ne laissez pas le travail défiler indéfiniment pendant que la page ajoute des éléments. Un rapport borné est plus facile à relire, à stocker et à comparer, et sa hauteur ne dépend pas du moment précis de la capture.
Éléments fixes et mise en page responsive
Les en-têtes collants, panneaux de consentement, boutons de discussion et barres flottantes peuvent couvrir le contenu d'une image pleine page. S'ils ne font pas partie du rapport, la route de capture doit proposer une présentation d'impression ou d'archive. Une petite feuille de style réservée à la capture est souvent plus claire que la suppression d'éléments après la création de l'image. Gardez le viewport du profil inchangé.
Utilisez le même viewport pour les exécutions comparables. Un changement de largeur modifie les retours à la ligne et la position des cartes même si les données sont identiques. Un changement du facteur de pixels de l'appareil modifie aussi les dimensions de sortie et la mémoire nécessaire pendant la création. Enregistrez ces valeurs dans les métadonnées de la capture, plutôt que de les laisser au hasard.
Vérifier la partie basse des images longues
La partie basse d'une image longue mérite une vérification séparée. Le haut peut sembler correct alors que le pied, la dernière ligne, les bordures ou le fond sont coupés. Un espace réservé au chargement peut aussi rester visible en bas. Ouvrir chaque image depuis son coin supérieur fait manquer ces problèmes.
Ajoutez à la page un marqueur de fin stable et incluez-le dans l'exemple de revue. Il peut s'agir du titre du pied de page, de la dernière section d'un rapport ou d'une zone de fin compréhensible pour le lecteur. Il doit être visible dans l'image finale, et non être un marqueur caché de l'exécution du navigateur.
Vérifiez le bord inférieur de trois façons :
- Ouvrez un aperçu réduit et confirmez que le document atteint la fin attendue.
- Ouvrez la dernière partie à une échelle lisible et vérifiez la dernière ligne,
le pied, les bordures et le remplissage du fond. Une capture de cette seule
bande dans Playwright s'écrit
page.screenshot({ clip: { x: 0, y: 800, width: 1280, height: 400 } }). - Comparez les dimensions et la taille du fichier avec le relevé de capture. Un
changement soudain peut signaler un déplacement de mise en page, une section
absente ou une ressource qui n'est pas arrivée.
identify -format '%wx%h %b' shot.pngimprime les deux valeurs du relevé.
Les contrôles automatiques doivent porter sur le contrat de la page et sur le fichier produit. Confirmez que le marqueur de fin était visible avant la capture, que le fichier existe et que l'image peut être décodée. Gardez quelques pages longues représentatives pour une revue visuelle. Une comparaison pixel par pixel peut aider dans une suite maîtrisée, mais accompagnez-la d'une revue lisible : les polices, le rendu de la plateforme et des changements de contenu légitimes peuvent modifier des pixels isolés.
N'utilisez pas la hauteur brute du document comme seule condition de succès. La hauteur peut être connue avant que le contenu soit peint et continuer à augmenter après la création de l'image. Le marqueur de fin et la revue du fichier répondent à la question utile : le contenu attendu est-il arrivé dans l'image finale ?
Prévoir la mémoire partagée pour les images hautes
La taille du conteneur fait partie de la configuration du serveur ; la configuration d'un serveur sans interface réunit les options et le chemin d'affichage qui l'accompagnent. Une capture longue consomme plus que la taille du PNG ou du JPEG sur le disque. Le navigateur a besoin d'espace de travail pour composer la page, la couche d'automatisation en a besoin pour recevoir le résultat et l'encodeur en utilise pendant l'écriture. Un profil à haute densité de pixels agrandit la surface dans les deux directions. Un montage de mémoire partagée trop petit peut échouer uniquement sur la page la plus longue et ressembler à un incident intermittent.
Réservez la mémoire partagée en fonction du travail réel. Tenez compte du viewport
du profil, du facteur de pixels, du document le plus long approuvé, du format d'image
et des captures qui peuvent coexister dans le conteneur. Laissez une marge pour le
démarrage du navigateur et le rendu courant. Configurez explicitement la taille de
/dev/shm avec le runtime du conteneur ou le réglage équivalent de Compose, au lieu
d'accepter la petite valeur par défaut.
docker run --rm \
--shm-size="${BROWSER_SHM_SIZE}" \
-e BROWSER_PROFILE=/run/profiles/capture.enc \
screenshot-worker
La valeur appartient à la configuration de déploiement, car elle dépend de l'ensemble de pages approuvé. Ne recopiez pas la configuration d'une page courte pour un travail de documents longs. Lorsque l'ensemble évolue, recapturez la page longue représentative et examinez le résultat ainsi que les ressources du conteneur.
D'autres ressources comptent également. Le dossier de sortie doit accepter l'image complète et son écriture temporaire, le volume doit avoir les droits du worker, et le stockage du profil doit être séparé des artefacts finaux. Une écriture échouée ne doit pas remplacer l'image approuvée précédente par un fichier partiel. Écrivez dans un nom temporaire sur le même volume, fermez le fichier, puis utilisez l'opération atomique habituelle du système de fichiers.
Si la configuration Linux approuvée utilise Xvfb, sa surface doit couvrir le viewport du profil et utiliser le réglage de couleur validé pour le déploiement. Le mode headless natif peut ne pas avoir besoin de Xvfb. N'ajoutez pas un écran virtuel parce que l'image est haute. Choisissez un chemin pris en charge, validez-le avec les pages cibles et gardez-le identique en développement et en production.
Choisir le format selon l'usage
Le PNG est un choix prudent pour les textes, diagrammes, interfaces et régressions visuelles. Il conserve les contours nets et n'ajoute pas d'artefacts autour des petits libellés. Le JPEG peut convenir aux aperçus riches en photos lorsque la taille du fichier compte davantage que la précision des contours. Inscrivez le format dans la spécification de capture pour qu'un autre travail ne change pas discrètement le critère de revue.
page.screenshot() de Playwright accepte type: 'png' ou type: 'jpeg', et
omitBackground: true ne conserve un fond transparent qu'en PNG. Puppeteer
expose aussi type: 'webp' avec un quality entre 0 et 100, utile quand le
destinataire décode le WebP.
Une sortie à haute densité de pixels est utile lorsque le lecteur doit agrandir un rapport ou lorsque l'image sert aux archives. Elle augmente aussi la mémoire, le transfert et le stockage. Le facteur de pixels doit venir du profil choisi. Ne le remplacez pas dans le contexte pour obtenir une image plus nette. Si le lecteur a besoin d'un aperçu plus petit, créez ce dérivé après avoir conservé la capture approuvée.
Utilisez des noms stables qui indiquent la route, la famille du profil, le mode, le format et la date, sans placer de données privées de la page dans le nom. Conservez l'artefact original lors de la création d'un dérivé. Une revue ultérieure pourra ainsi distinguer une modification de rendu d'une modification de miniature.
Un modèle de production court
La fonction de capture doit rendre l'ordre évident : créer une page liée au profil, naviguer, attendre le contrat de la page, utiliser la pleine page native si elle est demandée et fermer la page en cas d'échec.
async function capture(page, url, outputPath, fullPage = false) {
await page.goto(url, { waitUntil: 'domcontentloaded' });
await waitForCaptureReady(page);
await page.screenshot({
path: outputPath,
fullPage,
type: 'png',
});
}
Si plusieurs routes utilisent la même politique de session approuvée, créez le navigateur en dehors de cette fonction. Gardez la propriété des pages explicite, fermez une page après la fin de son artefact et transmettez l'erreur avec la route et le mode de capture. Une capture échouée doit être visible pour l'exécuteur, pas remplacée silencieusement par un ancien fichier.
Dans un lot, chaque page doit passer par les mêmes vérifications de disponibilité et d'artefact. N'ajoutez pas une voie rapide qui ignore le marqueur de fin pour les pages suivantes. La première page paraît souvent saine, tandis qu'une page lente révèle un manque de mémoire, une police tardive ou un document en croissance. Le même contrat facilite l'interprétation de ces différences.
Symptômes et ordre de vérification
L'image est vide ou presque vide. Vérifiez que la navigation est arrivée sur la
route attendue, que le marqueur prêt est apparu et que le chemin d'affichage choisi
était disponible. Examinez la sortie du navigateur pour savoir si la page s'est
arrêtée avant le marqueur, plutôt que d'ajouter une pause. La capture est un seul
aller-retour CDP : Page.captureScreenshot renvoie l'image entière dans une
chaîne base64, donc un transfert interrompu ne laisse aucun fichier partiel à
comparer.
L'image s'arrête avant la dernière section. Vérifiez le marqueur de fin, préparez le contenu différé avec un défilement normal et confirmez que le défilement infini a une limite. Examinez ensuite la partie basse de l'artefact.
La position du texte change entre deux captures. Vérifiez le même viewport du profil, facteur de pixels, polices, thème de couleur et données de page. Attendez la fin des polices et examinez les mouvements de mise en page avant la capture.
Le conteneur échoue seulement sur les pages longues. Vérifiez ensemble le
montage de mémoire partagée, l'espace temporaire, le volume de sortie et la surface
d'affichage. Une page courte ne prouve pas que la plus longue image tient. Ajustez
la ressource, recapturez la page représentative et conservez le réglage avec le
relevé du travail. --disable-dev-shm-usage déplace les fichiers de mémoire
partagée du navigateur vers /tmp ; cela change l'endroit où la pression
apparaît, pas si le viewport du profil et la surface de pixels tiennent.
Un contrôle flottant couvre le rapport. Utilisez la présentation d'archive de la route ou capturez un élément de contenu utile. Ne modifiez pas les pixels après coup et confirmez que la présentation garde le viewport du profil.
Un espace réservé au chargement reste dans l'image. Faites attendre au marqueur prêt la ressource nécessaire, ou déclarez le remplacement volontaire. L'état réseau général ne dit pas si un espace réservé convient au lecteur.
Deux tailles de fichier pour une même page
La même route, le même profil et le même script produisent un PNG d'environ 218 Ko à une exécution et d'environ 402 Ko à la suivante. Rien n'a échoué et les deux images s'ouvrent. Isolez la cause couche par couche au lieu de les comparer à l'œil.
- Comparez d'abord les dimensions en pixels avec
identify -format '%wx%h %b' shot.png. Des dimensions identiques avec une taille différente désignent le contenu, le texte ou la compression ; des dimensions différentes désignent le viewport. - Écartez le viewport. Lisez
window.innerWidthetwindow.devicePixelRatioà chaque exécution. Un facteur passé de2à1réduit la surface de pixels de moitié dans les deux sens et change la taille bien plus que n'importe quel changement de contenu. - Écartez le chargement des polices. Lisez
document.fonts.statuset le résultat dedocument.fonts.check('16px "Your Heading Family"')avant de capturer. Une exécution capturée avecstatus === 'loading'a peint des glyphes de remplacement, qui se compressent autrement et décalent les hauteurs de ligne. - Écartez l'animation. Appelez
document.getAnimations()et vérifiez qu'une longue transition ou un indicateur ontplayState === 'finished'. Une image de rendu capturée en cours de transition change l'anticrénelage et le remplissage de fond, donc l'encodeur produit un flux différent. - Comparez ensuite seulement le contenu. Comparez la zone de texte entre les deux fichiers ; si les mots diffèrent, les données ont changé et aucune capture n'est fausse.
- Gardez la correction dans le contrat de disponibilité : attendez la famille de polices et le marqueur de fin, et désactivez ou attendez l'animation non essentielle de la route de capture.
Localisez la couche avant de modifier le script. Une différence de taille dit seulement que la sortie a changé, pas laquelle de ces trois entrées a bougé. Deux valeurs mesurées par exécution, le viewport annoncé et l'état de polices, les séparent en une seule passe.
Vérifier avant de créer une référence
Conservez un petit ensemble représentatif de routes pour la revue du déploiement. Ajoutez une page courte, un rapport long, une page avec des polices web, une page avec des médias différés et une mise en page responsive utilisant le profil choisi. Le but n'est pas de réunir toutes les routes, mais de couvrir les états qui peuvent changer la limite de l'image ou la demande de ressources ; la validation des versions du navigateur conserve cet échantillon avec la compilation à laquelle il appartient.
Pour chaque route, notez le profil, la source du viewport, le mode, le format, le chemin d'affichage, la politique de mémoire du conteneur, le marqueur prêt et le marqueur de fin. Revoyez le premier écran et la partie basse. Confirmez que l'artefact peut être décodé, qu'il a l'orientation attendue et qu'il est écrit au bon endroit. Quand un changement est voulu, mettez aussi à jour le relevé de capture.
Une capture fiable est un flux de rendu, pas un dernier clic. Le profil fournit un viewport stable, la page fournit un état prêt compréhensible, l'option pleine page native atteint le contenu inférieur sans modifier le viewport et la revue vérifie la partie la plus facile à perdre. La mémoire partagée et le stockage complètent le contrat pour les documents longs et à haute densité de pixels. Les images restent ainsi utiles pour la revue visuelle, l'archivage et les contrôles de qualité autorisés dans différents environnements.
BotBrowser permet des captures répétables dans des contextes contrôlés; il ne garantit pas l'égalité visuelle entre hôtes, écrans, polices ou états.
Conclusion pratique
Traitez la capture comme un contrat de rendu : fixez viewport et entrées, attendez un état prêt visible, capturez nativement, vérifiez le bord inférieur et gardez un repli. Notez ce que BotBrowser a observé sans promettre que tous les hôtes, écrans, polices ou états coïncideront.
Sources
Pour aller plus loin, consultez validation des versions du navigateur et événements WebDriver BiDi.
Cet article fait partie du guide : Déploiement et livraison du navigateur : carte opérationnelle
Articles Connexes
Adapter la charge avec les profils Per-Context
DéploiementNavigateur automatisé dans Docker : déploiement et mise à l'échelle
DéploiementLimites de ressources d’un conteneur : méthode pratique
DéploiementBotBrowser headless sur serveurs Ubuntu
DéploiementSignaux headless et avec interface : méthode de comparaison
DéploiementSignaux d'automatisation et cohérence avec Playwright
Faites passer BotBrowser de la recherche à la production
Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.