Gestion des cookies du navigateur pour les flux multi-identités
Comment les cookies portent l'état de session et de consentement, pourquoi ceux ajoutés après la page manquent parfois la première requête et comment charger un jeu par identité.
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.
Les cookies sont de petits éléments d'état qu'un serveur demande au navigateur de conserver et de renvoyer avec les requêtes suivantes. Les sites s'en servent pour les sessions ouvertes, pour les préférences de langue et de thème et pour l'enregistrement d'un choix de consentement. Si vous testez un site en tant que développeur ou ingénieur QA, le stockage des cookies fait donc partie des données d'entrée du test. La même page se comporte différemment pour un nouveau visiteur, pour un visiteur récurrent qui a déjà accepté une bannière de consentement et pour un compte connecté. Un test qui ne maîtrise pas son jeu de cookies de départ mesure, sans le vouloir, l'un de ces cas.
Les sections suivantes suivent un cookie depuis l'en-tête du serveur jusqu'au stockage du navigateur, expliquent pourquoi les cookies ajoutés par une API de framework peuvent arriver après la première requête et montrent comment un jeu de cookies chargé au lancement avec l'option --bot-cookies de BotBrowser donne à chaque identité un état de départ documenté et reproductible. Elles nomment aussi ce qu'un stockage de cookies n'isole pas, car un fichier de cookies distinct n'est qu'une couche d'une identité distincte. Pour voir comment plusieurs comptes sont séparés au-delà des cookies, consultez Gestion multi-comptes des réseaux sociaux avec isolation du navigateur.
Le schéma compare deux ordres d'événements. Lorsque les cookies sont dans le stockage avant qu'une page n'existe, la première requête les envoie. Lorsqu'un script les ajoute après qu'une page a déjà commencé à se charger, la requête précédente part sans eux.
TL;DR
- Un cookie n'est expédié que si
Domain,Path,Secure,SameSiteet son expiration concordent avec la requête, si bien que de mauvais attributs ressemblent à un site qui ignore votre préparation. context.addCookies()etpage.setCookie()s'exécutent une fois le contexte ou la page créés, donc une requête partie avant ne transporte aucun cookie.--bot-cookiescharge le jeu avant la première navigation, depuis du JSON en ligne ou un fichier préfixé par@, et un cookie dépourvu du champurlest écarté sans message.- Un fichier de cookies par identité, accolé à son propre profil, rattache les tests de visiteur revenant et de consentement à un état documenté.
Sommaire
- Ce que porte un cookie et quels attributs influencent son envoi
- Pourquoi les cookies ajoutés par une API de framework peuvent manquer la première requête
- Charger au lancement un jeu de cookies par identité
- Garder séparées les identités et les contextes
- Vérifier que l'état de départ est celui que vous vouliez
- Où BotBrowser s'insère et où il s'arrête
- Conclusion pratique
Ce que porte un cookie et quels attributs influencent son envoi
La RFC 6265 définit le mécanisme. Un serveur envoie un en-tête de réponse Set-Cookie avec un nom, une valeur et des attributs facultatifs. Le navigateur enregistre le cookie et, pour les requêtes suivantes vers un hôte et un chemin correspondants, le renvoie dans un en-tête de requête Cookie. Les scripts de la page peuvent lire et écrire des cookies via document.cookie, sauf ceux marqués HttpOnly, que le navigateur garde hors de portée des scripts de la page. Le guide des cookies de MDN décrit le même déroulement et ajoute l'attribut SameSite, apparu après la spécification d'origine.
Cinq groupes d'attributs régissent l'envoi : Domain, Path, expiration, Secure et SameSite. HttpOnly ne modifie pas l'envoi ; il contrôle seulement si les scripts de la page peuvent lire le cookie.
- Domain et cookies liés à l'hôte seul. Un cookie défini sans attribut
Domainappartient exactement à l'hôte qui l'a défini. Un cookie avecDomain=example.comest aussi envoyé aux sous-domaines de cet hôte. - Path. Le cookie n'est envoyé que pour les chemins de requête qui correspondent au chemin enregistré.
- Expires et Max-Age. Un cookie sans aucun des deux est un cookie de session et doit prendre fin avec la session de navigation. Un cookie avec une date future est persistant et survit à un redémarrage.
- Secure. Le cookie n'est envoyé qu'en HTTPS.
- SameSite. Les valeurs
Strict,LaxetNonedéterminent si le cookie accompagne les requêtes intersites, etNones'emploie avecSecure.
Pour un test, les attributs comptent souvent plus que la valeur. Un cookie enregistré avec le mauvais domaine, le mauvais chemin ou avec Secure sur un site de test en HTTP simple est enregistré sans erreur puis n'est jamais envoyé, ce qui donne l'impression que le site ignore votre configuration. La RFC 6265 ne fixe que des limites minimales sur le nombre de cookies conservés par un navigateur et laisse l'éviction à l'implémentation, et les navigateurs ont fait évoluer leurs valeurs par défaut pour des attributs comme SameSite. Prenez la spécification et le guide MDN comme références, et confirmez le comportement dans la version du navigateur avec laquelle vous testez réellement.
Les cookies de consentement demandent une précision de plus. Il n'existe ni nom ni valeur standard pour un choix de consentement. Chaque site ou plateforme de consentement définit son propre cookie, son propre format de valeur et sa propre durée de vie, et les noms et valeurs des exemples ci-dessous sont illustratifs. Pour construire un état de départ réaliste, faites le choix une fois dans une session de navigateur ordinaire que vous maîtrisez, lisez le cookie que le site définit en réponse et copiez son nom, le format de sa valeur, son domaine, son chemin et son expiration. Un cookie inventé au hasard peut simplement être ignoré par le site, et le test affiche alors une bannière que vous pensiez avoir fermée.
Pourquoi les cookies ajoutés par une API de framework peuvent manquer la première requête
Les frameworks d'automatisation offrent leurs propres appels pour les cookies. Playwright a context.addCookies() et Puppeteer a page.setCookie(). Ce sont deux appels que votre script effectue une fois que le contexte du navigateur ou la page existent déjà. Le navigateur démarre, le contexte et la page sont créés, et ce n'est qu'ensuite que votre appel arrive. À partir de ce moment, les cookies sont dans le stockage. Une requête envoyée avant l'appel n'en porte aucun.
Dans beaucoup de scripts, cet écart est sans conséquence. Si vous créez un contexte, ajoutez les cookies et n'appelez goto() qu'ensuite pour la première fois, la première navigation les porte déjà. L'écart apparaît quand quelque chose se charge avant votre appel. Une page que le navigateur ou un lanceur ouvre au démarrage peut commencer à se charger toute seule. Une fixture de l'exécuteur de tests peut créer la page à un endroit et ajouter les cookies à un autre. Une fonction utilitaire partagée peut ouvrir la première page à votre place. Dans chaque cas, la toute première requête part alors que le stockage des cookies est encore vide, et l'ordre que vous obtenez peut dépendre d'un minutage que vous ne maîtrisez pas.
Les tests de consentement et de visiteur récurrent sont les plus sensibles. La première réponse est souvent celle qui décide si le serveur affiche une bannière de consentement ou émet un nouveau cookie de session pour un visiteur qu'il n'a jamais vu. Si cette réponse est produite pour une requête sans cookies, le serveur peut définir son propre cookie, et les cookies que vous ajoutez un instant plus tard peuvent entrer en conflit avec lui ou arriver trop tard pour compter. Le test rend alors compte de ce qui arrive à un nouveau visiteur, alors que l'intention était de tester un visiteur récurrent.
Le chargement au lancement change l'ordre. Selon la documentation de BotBrowser, les cookies transmis avec --bot-cookies sont chargés avant la première navigation de page, de sorte que la première requête HTTP les inclut déjà. C'est la seule comparaison faite ici : le moment où les cookies deviennent disponibles par rapport à la première requête. Les API des frameworks restent utiles pour modifier des cookies en cours de test, et il n'est pas nécessaire de les remplacer pour cela.
Pour savoir si l'écart touche votre configuration, n'enregistrez que la présence de l'en-tête Cookie et les noms qu'il contient dès le démarrage du navigateur. Comparez les premières entrées aux noms attendus ; ne conservez jamais l'en-tête brut, car il peut contenir un identifiant de session. Si la première requête porte déjà les noms attendus, l'ordre est correct et un appel de framework suffit. Si les premières entrées ne contiennent aucun nom, déplacez ce jeu de cookies dans l'option de lancement.
Charger au lancement un jeu de cookies par identité
L'option --bot-cookies nécessite le niveau PRO. Elle accepte un tableau JSON d'objets cookie, soit en ligne dans la valeur de l'option, soit depuis un fichier lorsque la valeur commence par @. Le JSON en ligne convient à un petit jeu, comme un unique cookie de consentement :
chromium-browser \
--bot-profile="path/to/profile.enc" \
--bot-cookies='[{"url":"https://example.com","name":"consent","value":"accepted","domain":".example.com"}]'
Un fichier convient à un jeu plus important et garde la commande de lancement lisible. Le conseil de dépannage de la documentation est d'utiliser un chemin absolu après le @ :
[
{
"url": "https://example.com",
"name": "session_id",
"value": "abc123",
"domain": ".example.com",
"path": "/",
"secure": true,
"httpOnly": true,
"expirationDate": 1893456000
},
{
"url": "https://example.com",
"name": "locale",
"value": "en-US",
"domain": ".example.com",
"path": "/"
}
]
Chaque objet cookie accepte ces champs, tels que la documentation les énumère :
- url (obligatoire) : l'URL complète utilisée pour définir le cookie, par exemple
https://example.com. Un cookie qui en est dépourvu est ignoré silencieusement. - name et value (obligatoires) : les données du cookie lui-même.
- domain : le domaine auquel appartient le cookie. Un point initial, comme dans
.example.com, inclut les sous-domaines. - path :
/par défaut. - secure :
truepar défaut ; sur un site de test en HTTP clair, passez-le explicitement àfalse, sinon le cookie est stocké puis jamais expédié. - httpOnly :
falsepar défaut. - sameSite :
strict,laxounone. - expirationDate : un horodatage Unix en secondes depuis l'époque.
Trois détails causent l'essentiel des confusions. Le premier est le champ url. Ignorer un cookie qui en est dépourvu se fait en silence : un seul champ manquant ne produit aucune erreur et donne un jeu de cookies plus petit que prévu. Comptez les cookies après le lancement plutôt que de supposer qu'ils ont été chargés. Le deuxième est secure, qui vaut true par défaut. Selon notre lecture de cette valeur par défaut, si votre site de test fonctionne en HTTP simple, mettez-le explicitement à false, sinon le cookie est enregistré et jamais envoyé. Le troisième est la manière de construire l'option dans le code. Transmettez comme valeur le résultat de JSON.stringify() sans l'entourer de guillemets supplémentaires, car ceux-ci font partie de la valeur et le JSON ne peut plus être analysé. Utilisez aussi un expirationDate futur en secondes, et non en millisecondes, pour que le cookie n'expire pas dès son chargement.
Un lancement Playwright avec une identité, un profil et un fichier de cookies ressemble à ceci :
import { chromium } from 'playwright-core';
const browser = await chromium.launch({
executablePath: process.env.BOTBROWSER_EXEC_PATH,
headless: true,
args: ['--bot-profile=profiles/identity-a.enc', '--bot-cookies=@/absolute/path/cookies/identity-a.json'],
});
const page = await browser.newPage();
await page.goto('https://example.com/');
Garder séparées les identités et les contextes
Gardez un fichier de cookies par identité et nommez-le d'après cette identité. Associez-le au profil propre de l'identité et ne copiez pas un fichier d'une identité à l'autre, car un jeu copié emporte les choix de consentement et les enregistrements de session de la première identité dans la seconde. Lorsque vous faites tourner un profil, relisez le fichier de cookies en même temps, car un enregistrement de consentement ou une préférence de langue qui correspondait à l'ancien profil peut ne plus correspondre au nouveau. Conserver les fichiers dans un seul dossier documenté, avec un accès limité aux personnes qui lancent les tests, donne aussi à un relecteur quelque chose de concret à comparer d'une exécution à l'autre, ce qui est le but d'un état de départ reproductible. Mélanger les fichiers rend les résultats difficiles à attribuer : si un test de consentement et un test avec compte connecté partagent un fichier, un échec peut venir de l'un ou l'autre état, et personne ne peut dire lequel sans relancer les deux. Des fichiers séparés transforment cette question en simple consultation. Le guide d'isolation multi-comptes et le guide de gestion des profils expliquent comment organiser les profils et les fichiers qui les accompagnent.
La portée de l'option dépend de la façon dont vous la transmettez. La documentation de BotBrowser indique que les cookies chargés au lancement deviennent l'état de départ du navigateur. Avec Puppeteer, utilisez browser.defaultBrowserContext() pour atteindre ce contexte, car context.cookies() sur un autre contexte peut renvoyer une liste vide. Avec Per-Context Fingerprint, --bot-cookies peut au contraire être transmis via botbrowserFlags lors de la création d'un BrowserContext, et les cookies sont alors importés dans ce seul contexte. Chaque contexte démarre avec son propre état de cookies tout en partageant un seul processus de navigateur :
const client = await browser.newBrowserCDPSession();
const cookies = JSON.stringify([
{ url: 'https://example.com', name: 'consent', value: 'accepted', domain: '.example.com', path: '/' },
]);
const { browserContextId } = await client.send('Target.createBrowserContext', {
botbrowserFlags: ['--bot-profile=profiles/identity-a.enc', '--bot-cookies=' + cookies],
});
Créez les pages seulement après l'existence du contexte, afin que la première navigation utilise déjà l'état de cookies du contexte.
Un stockage de cookies n'isole pas à lui seul tout ce qui rend deux identités indépendantes. La RFC 6265 est explicite sur plusieurs limites, et les autres découlent de ce que l'option documente :
- Les cookies ne sont pas isolés par port, de sorte que deux services sur le même hôte partagent les cookies de cet hôte.
- Un cookie d'un domaine parent est visible de tous les sous-domaines, et un sous-domaine frère peut définir un cookie qu'un autre lira.
- Le stockage local, IndexedDB, les caches et les service workers sont des stockages distincts. La documentation de
--bot-cookiesne décrit pas leur préchargement. - Un fichier de cookies ne dit rien du profil d'empreinte, des réglages de langue ni de la route réseau qui accompagnent l'identité.
Le guide du stockage du navigateur explique en quoi ces autres stockages diffèrent des cookies. Si une identité doit démarrer sans état résiduel, vérifiez aussi ce qu'emporte un --user-data-dir réutilisé, car il conserve tous ces stockages ensemble, pas seulement les cookies.
Vérifier que l'état de départ est celui que vous vouliez
Trois vérifications couvrent l'état de départ : ce qui se trouve dans le stockage, ce que porte la première requête et ce que la page peut lire. Cette esquisse Playwright lit l'état stocké ; le contrôle suivant inspecte la requête la plus précoce :
await page.goto('https://example.com/');
console.log(await page.context().cookies('https://example.com/'));
Comparer les noms de cookies sans journaliser les valeurs
La présence d’un en-tête Cookie n’est que le premier contrôle. Pour le répéter, comparez les noms que vous vouliez charger aux noms observés dans la première requête, sans conserver leurs valeurs dans les journaux :
const expectedNames = new Set(['session_id', 'locale']);
let firstRequestSeen = false;
let firstCookieNames = [];
page.on('request', request => {
if (firstRequestSeen) return;
firstRequestSeen = true;
const header = request.headers().cookie || '';
firstCookieNames = header
.split(';')
.map(part => part.split('=')[0].trim())
.filter(Boolean);
});
await page.goto('https://example.com/');
const actualNames = new Set(firstCookieNames);
console.log(
JSON.stringify({
headerPresent: firstRequestSeen && actualNames.size > 0,
expected: [...expectedNames].sort(),
actual: [...actualNames].sort(),
missing: [...expectedNames].filter(name => !actualNames.has(name)).sort(),
})
);
Une liste missing vide prouve que les noms de cookies sont arrivés dans la première requête, pas que leurs valeurs ou la politique de session du site sont valides. Si un nom manque, vérifiez url, le domaine, le chemin, secure, l’expiration et l’ordre de démarrage avant d’inspecter une réponse applicative.
Enregistrez l'écouteur de requêtes avant la première navigation, afin que la requête la plus précoce soit incluse. La liste de cookies doit contenir tous les cookies de votre fichier et la première requête doit montrer un en-tête de cookies. Si le nombre est inférieur à celui du fichier, cherchez d'abord un champ url manquant. Gardez à l'esprit qu'un cookie marqué HttpOnly n'apparaît pas dans document.cookie. C'est ainsi que l'attribut est défini, donc son absence dans la page n'est pas un échec. Pour ces cookies, vérifiez le stockage et l'en-tête de la requête.
Comparez les indicateurs des cookies avec ce que le site cible définit réellement. Ouvrez le site normalement dans un profil de navigateur jetable, regardez ses en-têtes Set-Cookie dans le panneau réseau et faites correspondre Domain, Path, Secure, HttpOnly, SameSite et l'expiration. Un cookie préchargé dont les indicateurs diffèrent du cookie réel peut être traité autrement que le cookie que le site aurait émis, et le test couvre alors un état que les vrais visiteurs n'ont jamais.
Quand un cookie n'arrive pas, passez en revue les causes dans l'ordre où elles se présentent habituellement. D'abord, vérifiez le champ url, car un cookie ignoré ne laisse aucune trace. Ensuite, comparez domain et path avec l'URL de la requête, en vous rappelant qu'un cookie lié à l'hôte seul n'atteint pas les sous-domaines. Troisièmement, regardez secure et le schéma du site. Quatrièmement, pensez à sameSite, car un cookie marqué lax ou strict peut ne pas accompagner une requête intersite. Cinquièmement, confirmez que l'expiration est dans le futur et en secondes. Sixièmement, assurez-vous que la valeur de l'option est un JSON valide sans guillemets supplémentaires et que le chemin du fichier est absolu. Enfin, avec Puppeteer, lisez les cookies depuis le contexte de navigateur par défaut.
Un court dossier de revue garde la configuration reproductible. Pour chaque identité, notez :
- l'origine des valeurs des cookies, par exemple un état de consentement enregistré dans un compte de test ou documenté à la main ;
- le fichier attribué à cette identité et le profil auquel il est associé ;
- l'expiration et les indicateurs, alignés sur le comportement de
Set-Cookiedu site cible ; - la façon dont vous avez vérifié l'état préchargé, et avec quelle version du navigateur.
Si vous exportez des cookies à la fin d'une session pour les réutiliser, notez que les objets cookie renvoyés par un framework peuvent ne pas avoir de champ url. Ajoutez-en un à chaque objet avant de passer le fichier à --bot-cookies, sinon le cookie est ignoré. Ne chargez que des cookies appartenant à vos propres comptes de test ou à des états de consentement documentés. Extraire, forger ou rejouer les cookies de session d'un tiers est hors du cadre de cette configuration, et un site peut de toute façon rejeter ou faire expirer un cookie injecté. Répétez la vérification chaque fois que la version du navigateur, le profil ou le fichier de cookies change, car une différence sur la première requête s'explique plus facilement quand un seul de ces éléments a changé depuis la dernière exécution réussie.
Où BotBrowser s'insère et où il s'arrête
BotBrowser documente le chargement des cookies au lancement avec --bot-cookies (niveau PRO), à partir d'un JSON en ligne ou d'un fichier @, avant la première navigation de page afin que la première requête HTTP les porte déjà, et leur importation dans un seul BrowserContext lorsque l'option est transmise via botbrowserFlags, de sorte que les équipes puissent reproduire un état initial de cookies documenté pour les tests de visiteur récurrent et de parcours de consentement et garder les identités séparées. BotBrowser ne modifie pas la spécification des cookies ni les règles SameSite et Secure, ignore silencieusement les cookies dépourvus de champ url, ne documente pas le préchargement de localStorage ni d'IndexedDB et ne garantit pas qu'un site cible accepte, conserve ou rattache à une session un cookie injecté.
Sources
- RFC 6265: HTTP State Management Mechanism
- MDN : Using HTTP cookies
- Documentation BotBrowser : Cookie Management
Conclusion pratique
Partez de la requête, pas de l'option : notez les noms de la toute première requête et comparez-les au fichier chargé, sans conserver d'en-têtes Cookie bruts. Si la requête porte les noms attendus, l'ordre est bon et un appel de framework suffit ; sinon, déplacez le jeu vers --bot-cookies et chargez-le au lancement. Confrontez Domain, Path, Secure, SameSite et l'expiration aux en-têtes Set-Cookie réellement renvoyés par le site, car un cookie stocké avec de mauvais attributs n'est jamais expédié et produit un test qui mesure en silence un visiteur de première visite. Comptez les cookies après le lancement au lieu de vous fier à la commande, un champ url absent en écartant un sans erreur, et employez un chemin absolu après @. Gardez un fichier par identité à côté du profil de cette identité et ajoutez url à chaque cookie exporté avant de le réutiliser. Notez l'origine des valeurs, les attributs, la version du navigateur et la façon dont vous avez vérifié l'état préchargé, afin que l'exécution suivante ne diffère que sur un champ connu.
Cet article fait partie du guide : Surfaces d’identité du navigateur : vue de cohérence pratique
Articles Connexes
Cookie Store API : cookies asynchrones et confidentialité
IdentitéCookies partitionnés (CHIPS) : confidentialité et sessions
IdentitéFedCM et confidentialité du navigateur : parcours de compte
IdentitéCookies, localStorage et IndexedDB : où ranger l’état
IdentitéCredential Management API : concevoir une connexion fiable
DémarrageRecettes CLI du navigateur automatisé : exemples de commandes
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.