Gestión de cookies del navegador para flujos multi-identidad
Cómo guardan las cookies el estado de sesión y consentimiento, por qué las añadidas tras crear la página pueden perder la primera petición y cómo cargar un conjunto por identidad.
BotBrowser Team
Prefieres la documentación del producto mantenida?
Este artículo tiene una página equivalente en el centro de documentación. Usa los docs para el flujo canónico, las flags actuales y la referencia duradera.
Las cookies son pequeños fragmentos de estado que un servidor pide al navegador que guarde y devuelva en las peticiones posteriores. Los sitios las usan para las sesiones iniciadas, para las preferencias de idioma y de tema y para el registro de una decisión de consentimiento. Si pruebas un sitio como desarrollador o ingeniero de QA, eso convierte el almacén de cookies en parte de los datos de entrada de la prueba. La misma página se comporta de forma distinta para un visitante nuevo, para un visitante recurrente que ya aceptó un banner de consentimiento y para una cuenta con sesión iniciada. Una prueba que no controla su conjunto inicial de cookies está midiendo, sin querer, uno de esos casos.
Las secciones siguientes siguen una cookie desde la cabecera del servidor hasta el almacén del navegador, explican por qué las cookies añadidas con una API de framework pueden llegar después de la primera petición y muestran cómo un conjunto de cookies cargado al iniciar con la bandera --bot-cookies de BotBrowser da a cada identidad un estado inicial documentado y repetible. También nombran lo que un almacén de cookies no aísla, porque un archivo de cookies separado es solo una capa de una identidad separada. Para saber cómo se mantienen separadas varias cuentas más allá de las cookies, consulta Varias cuentas de redes sociales con aislamiento del navegador.
El diagrama compara dos órdenes de eventos. Cuando las cookies están en el almacén antes de que exista ninguna página, la primera petición las lleva. Cuando un script las añade después de que una página ya empezó a cargar, la petición anterior sale sin ellas.
TL;DR
- Una cookie solo se envía cuando
Domain,Path,Secure,SameSitey su caducidad encajan con la petición, así que atributos equivocados parecen un sitio que ignora tu configuración. context.addCookies()ypage.setCookie()se ejecutan después de que existan el contexto o la página, así que una petición que sale antes no lleva ninguna cookie.--bot-cookiescarga el conjunto antes de la primera navegación, desde JSON en línea o un archivo con@, y una cookie sin el campourlse omite en silencio.- Un archivo de cookies por identidad, junto a su propio perfil, mantiene las pruebas de visitante recurrente y de consentimiento atribuibles a un estado documentado.
Contenido
- Qué lleva una cookie y qué atributos afectan a su envío
- Por qué las cookies añadidas con una API del framework pueden perder la primera petición
- Cargar al iniciar un conjunto de cookies por identidad
- Mantener separadas las identidades y los contextos
- Comprobar que el estado inicial es el que querías
- Dónde encaja BotBrowser y dónde termina
- Conclusión práctica
Qué lleva una cookie y qué atributos afectan a su envío
La RFC 6265 define el mecanismo. Un servidor envía una cabecera de respuesta Set-Cookie con un nombre, un valor y atributos opcionales. El navegador guarda la cookie y, en las peticiones posteriores a un host y una ruta que coincidan, la devuelve en una cabecera de petición Cookie. Los scripts de la página pueden leer y escribir cookies mediante document.cookie, salvo las marcadas como HttpOnly, que el navegador mantiene fuera del alcance de los scripts de la página. La guía de cookies de MDN describe el mismo flujo y añade el atributo SameSite, que llegó después de la especificación original.
Cinco grupos de atributos determinan el envío: Domain, Path, caducidad, Secure y SameSite. HttpOnly no afecta al envío; solo controla si los scripts de la página pueden leer la cookie.
- Domain y cookies solo de host. Una cookie definida sin atributo
Domainpertenece exactamente al host que la definió. Una cookie conDomain=example.comse envía también a los subdominios de ese host. - Path. La cookie se envía solo para las rutas de petición que coinciden con la ruta guardada.
- Expires y Max-Age. Una cookie sin ninguno de los dos es una cookie de sesión y está pensada para terminar con la sesión de navegación. Una cookie con una fecha futura es persistente y sobrevive a un reinicio.
- Secure. La cookie se envía solo por HTTPS.
- SameSite. Los valores
Strict,LaxyNonecontrolan si la cookie viaja con peticiones entre sitios, yNonese usa junto conSecure.
En una prueba, los atributos suelen importar más que el valor. Una cookie guardada con el dominio equivocado, con la ruta equivocada o con Secure en un sitio de prueba HTTP simple se guarda sin error y luego nunca se envía, lo que parece que el sitio ignora tu configuración. La RFC 6265 fija solo límites mínimos sobre cuántas cookies conserva un navegador y deja el desalojo a cada implementación, y los navegadores han cambiado con el tiempo sus valores por defecto para atributos como SameSite. Toma como referencia tanto la especificación como la guía de MDN, y confirma el comportamiento en la versión del navegador con la que pruebas de verdad.
Las cookies de consentimiento requieren una nota más. No existe un nombre ni un valor estándar para una decisión de consentimiento. Cada sitio o plataforma de consentimiento define su propia cookie, su propio formato de valor y su propia duración, y los nombres y valores de los ejemplos siguientes son ilustrativos. Para construir un estado inicial realista, toma la decisión una vez en una sesión de navegador normal que controles, lee la cookie que el sitio define como respuesta y copia su nombre, el formato de su valor, el dominio, la ruta y la caducidad. Una cookie que inventes por intuición puede ser ignorada sin más por el sitio, y entonces la prueba muestra un banner que creías haber cerrado.
Por qué las cookies añadidas con una API del framework pueden perder la primera petición
Los frameworks de automatización ofrecen sus propias llamadas para cookies. Playwright tiene context.addCookies() y Puppeteer tiene page.setCookie(). Ambas son llamadas que tu script hace cuando el contexto del navegador o la página ya existen. El navegador arranca, se crean el contexto y la página, y solo entonces llega tu llamada. Desde ese momento las cookies están en el almacén. Una petición enviada antes de la llamada no lleva ninguna de ellas.
En muchos scripts ese hueco es inofensivo. Si creas un contexto, añades cookies y solo después llamas a goto() por primera vez, la primera navegación ya las lleva. El hueco aparece cuando algo se carga antes de tu llamada. Una página que el navegador o un lanzador abre al arrancar puede empezar a cargar por su cuenta. Un fixture del ejecutor de pruebas puede crear la página en un sitio y añadir las cookies en otro. Un helper compartido puede abrir la primera página por ti. En cada caso la primera petición sale mientras el almacén de cookies sigue vacío, y cuál de esos órdenes obtienes puede depender de tiempos que no controlas.
Las pruebas de consentimiento y de visitante recurrente son las que más lo notan. La primera respuesta suele ser la que decide si el servidor muestra un banner de consentimiento o emite una cookie de sesión nueva para un visitante que nunca ha visto. Si esa respuesta se genera para una petición sin cookies, el servidor puede definir su propia cookie, y las cookies que añades un momento después pueden chocar con ella o llegar demasiado tarde para importar. La prueba informa entonces de lo que le pasó a un visitante nuevo aunque la intención era probar uno recurrente.
La carga al iniciar cambia el orden. Según la documentación de BotBrowser, las cookies pasadas con --bot-cookies se cargan antes de la primera navegación de página, de modo que la primera petición HTTP ya las incluye. Esa es la única comparación que se hace aquí: cuándo están disponibles las cookies respecto a la primera petición. Las API de los frameworks siguen siendo útiles para cambiar cookies en mitad de una prueba, y no hace falta sustituirlas para eso.
Para saber si el hueco afecta a tu configuración, registra solo si cada petición tiene cabecera Cookie y qué nombres contiene desde que arranca el navegador. Compara las primeras entradas con los nombres previstos; nunca guardes la cabecera sin ocultar, porque puede contener una credencial de sesión. Si la primera petición ya lleva los nombres esperados, el orden es correcto y basta una llamada del framework. Si las primeras entradas no tienen nombres, pasa ese conjunto de cookies a la bandera de inicio.
Cargar al iniciar un conjunto de cookies por identidad
La bandera --bot-cookies requiere el nivel PRO. Acepta un array JSON de objetos de cookie, ya sea en línea en el valor de la bandera o desde un archivo cuando el valor empieza por @. El JSON en línea sirve para un conjunto pequeño, como una única cookie de consentimiento:
chromium-browser \
--bot-profile="path/to/profile.enc" \
--bot-cookies='[{"url":"https://example.com","name":"consent","value":"accepted","domain":".example.com"}]'
Un archivo sirve para un conjunto mayor y mantiene legible el comando de inicio. El consejo de resolución de problemas de la documentación es usar una ruta absoluta después de la @:
[
{
"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": "/"
}
]
Cada objeto de cookie admite estos campos, según figuran en la documentación:
- url (obligatorio): la URL completa usada para definir la cookie, por ejemplo
https://example.com. Una cookie sin él se omite en silencio. - name y value (obligatorios): los datos de la propia cookie.
- domain: el dominio al que pertenece la cookie. Un punto inicial, como en
.example.com, incluye los subdominios. - path: por defecto
/. - secure: por defecto
true, de modo que la cookie se envía solo por HTTPS. - httpOnly: por defecto
false. - sameSite:
strict,laxonone. - expirationDate: una marca de tiempo Unix en segundos desde la época.
Tres detalles causan la mayor parte de la confusión. El primero es el campo url. Omitir una cookie que no lo tiene es silencioso, así que un solo campo ausente no produce ningún error y deja un conjunto de cookies menor del esperado. Cuenta las cookies después de iniciar en lugar de dar por hecho que se cargaron. El segundo es secure, que por defecto es true. En nuestra lectura de ese valor por defecto, si tu sitio de prueba funciona por HTTP simple, ponlo en false de forma explícita, o la cookie se guarda y nunca se envía. El tercero es la forma de construir la bandera en el código. Pasa como valor la salida de JSON.stringify() sin envolverla en comillas adicionales, porque las comillas adicionales pasan a formar parte del valor y el JSON deja de analizarse. Usa además un expirationDate futuro en segundos, no en milisegundos, para que la cookie no caduque en cuanto se carga.
Un inicio con Playwright con una identidad, un perfil y un archivo de cookies tiene este aspecto:
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/');
Mantener separadas las identidades y los contextos
Mantén un archivo de cookies por identidad y ponle el nombre de esa identidad. Empareja cada archivo con el perfil propio de la identidad y no copies un archivo de una identidad a otra, porque un conjunto copiado lleva las decisiones de consentimiento y los registros de sesión de la primera identidad a la segunda. Cuando rotes un perfil, revisa el archivo de cookies a la vez, ya que un registro de consentimiento o una preferencia de idioma que encajaba con el perfil antiguo puede no encajar con el nuevo. Guardar los archivos en una sola carpeta documentada, con acceso limitado a las personas que ejecutan las pruebas, también da a quien revisa algo concreto que comparar entre ejecuciones, que es el objetivo de un estado inicial repetible. Mezclar archivos dificulta atribuir los resultados: si una prueba de consentimiento y una de sesión iniciada comparten un archivo, un fallo puede venir de cualquiera de los dos estados y nadie sabe cuál sin repetir ambas. Archivos separados convierten esa pregunta en una consulta. La guía de aislamiento multicuenta y la guía de gestión de perfiles explican cómo organizar los perfiles y los archivos que los acompañan.
El alcance de la bandera depende de cómo la pases. La documentación de BotBrowser dice que las cookies cargadas al iniciar pasan a ser el estado inicial del navegador. Con Puppeteer, usa browser.defaultBrowserContext() para llegar a ese contexto, ya que context.cookies() en otro contexto puede devolver una lista vacía. Con Per-Context Fingerprint, --bot-cookies puede pasarse en cambio mediante botbrowserFlags al crear un BrowserContext, y entonces las cookies se importan solo en ese contexto. Cada contexto empieza con su propio estado de cookies mientras comparte un único proceso del navegador:
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],
});
Crea las páginas solo después de que exista el contexto, de modo que la primera navegación ya use el estado de cookies del contexto.
Un almacén de cookies por sí solo no aísla todo lo que hace independientes a dos identidades. La RFC 6265 es explícita sobre varios límites, y los demás se deducen de lo que documenta la bandera:
- Las cookies no se aíslan por puerto, así que dos servicios en el mismo host comparten las cookies de ese host.
- Una cookie de un dominio padre es visible para todos los subdominios, y un subdominio hermano puede definir una cookie que otro leerá.
- Local storage, IndexedDB, las cachés y los service workers son almacenes aparte. La documentación de
--bot-cookiesno describe su precarga. - Un archivo de cookies no dice nada sobre el perfil de huella, los ajustes de idioma ni la ruta de red que acompañan a la identidad.
La guía de almacenamiento del navegador explica en qué se diferencian esos otros almacenes de las cookies. Si una identidad debe empezar sin estado residual, comprueba también qué arrastra un --user-data-dir reutilizado, porque conserva todos esos almacenes juntos, no solo las cookies.
Comprobar que el estado inicial es el que querías
Tres comprobaciones cubren el estado inicial: qué hay en el almacén, qué lleva la primera petición y qué puede leer la página. Este esquema de Playwright lee el estado almacenado; la siguiente comprobación inspecciona la petición más temprana:
await page.goto('https://example.com/');
console.log(await page.context().cookies('https://example.com/'));
Comparar nombres de cookies sin registrar valores
La presencia de una cabecera Cookie es solo la primera comprobación. Para repetirla, compara los nombres que querías cargar con los nombres observados en la primera petición, sin guardar sus valores en los registros:
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(),
})
);
Una lista missing vacía demuestra que los nombres de cookies llegaron a la primera petición, no que sus valores o la política de sesión del sitio sean válidos. Si falta un nombre, revisa url, dominio, ruta, secure, caducidad y orden de inicio antes de inspeccionar una respuesta de la aplicación.
Registra el listener de peticiones antes de la primera navegación, para que se incluya la petición más temprana. La lista de cookies debe contener todas las cookies de tu archivo y la primera petición debe mostrar una cabecera de cookies. Si el recuento es menor que el del archivo, busca antes que nada un campo url ausente. Ten en cuenta que una cookie marcada como HttpOnly no aparecerá en document.cookie. Así está definido el atributo, de modo que su ausencia en la página no es un fallo. Para esas cookies comprueba el almacén y la cabecera de la petición.
Compara los indicadores de las cookies con lo que el sitio de destino define de verdad. Abre el sitio de forma normal en un perfil de navegador desechable, mira sus cabeceras Set-Cookie en el panel de red y haz coincidir Domain, Path, Secure, HttpOnly, SameSite y la caducidad. Una cookie precargada cuyos indicadores difieren de la real puede tratarse de otra manera que la cookie que habría emitido el sitio, y entonces la prueba cubre un estado que los visitantes reales nunca tienen.
Cuando una cookie no llega, repasa las causas en el orden en que suelen darse. Primero, revisa el campo url, porque una cookie omitida no deja rastro. Segundo, compara domain y path con la URL de la petición, recordando que una cookie solo de host no llega a los subdominios. Tercero, mira secure y el esquema del sitio. Cuarto, considera sameSite, ya que una cookie marcada como lax o strict puede no viajar con una petición entre sitios. Quinto, confirma que la caducidad está en el futuro y en segundos. Sexto, asegúrate de que el valor de la bandera es JSON válido sin comillas adicionales y de que la ruta del archivo es absoluta. Por último, con Puppeteer, lee las cookies del contexto de navegador por defecto.
Un breve registro de revisión mantiene la configuración repetible. Para cada identidad, anota:
- el origen de los valores de las cookies, por ejemplo un estado de consentimiento registrado en una cuenta de prueba o documentado a mano;
- el archivo asignado a esa identidad y el perfil con el que se empareja;
- la caducidad y los indicadores, ajustados al comportamiento de
Set-Cookiedel sitio de destino; - cómo verificaste el estado precargado y con qué versión del navegador.
Si exportas cookies al final de una sesión para reutilizarlas, ten en cuenta que los objetos de cookie que devuelve un framework pueden no incluir un campo url. Añade uno a cada objeto antes de pasar el archivo a --bot-cookies, o la cookie se omite. Carga solo cookies que pertenezcan a tus propias cuentas de prueba o a estados de consentimiento documentados. Extraer, falsificar o reproducir las cookies de sesión de otra parte queda fuera del alcance de esta configuración, y un sitio puede rechazar o caducar cualquier cookie inyectada de todos modos. Repite la verificación siempre que cambie la versión del navegador, el perfil o el archivo de cookies, porque una diferencia en la primera petición es más fácil de explicar cuando solo cambió una de esas cosas desde la última ejecución correcta.
Dónde encaja BotBrowser y dónde termina
BotBrowser documenta la carga de cookies al iniciar con --bot-cookies (nivel PRO), desde JSON en línea o un archivo con @, antes de la primera navegación de página para que la primera petición HTTP ya las lleve, y su importación en un único BrowserContext cuando la bandera se pasa mediante botbrowserFlags, de modo que los equipos puedan repetir un estado inicial de cookies documentado para pruebas de visitante recurrente y de flujos de consentimiento y mantener separadas las identidades. BotBrowser no cambia la especificación de cookies ni las reglas de SameSite y Secure, omite en silencio las cookies que no tienen el campo url, no documenta la precarga de localStorage ni de IndexedDB y no garantiza que un sitio de destino acepte, conserve o vincule a una sesión ninguna cookie inyectada.
Fuentes
- RFC 6265: HTTP State Management Mechanism
- MDN: Using HTTP cookies
- Documentación de BotBrowser: Cookie Management
Conclusión práctica
Empieza por la petición, no por la bandera: registra los nombres de la petición más temprana y compáralos con el archivo cargado, sin conservar cabeceras Cookie sin ocultar. Si la petición lleva los nombres esperados, el orden es correcto y basta una llamada del framework; si no, mueve el conjunto a --bot-cookies y cárgalo al iniciar. Contrasta Domain, Path, Secure, SameSite y la caducidad con las cabeceras Set-Cookie que el sitio devuelve de verdad, porque una cookie guardada con atributos equivocados nunca se envía y produce una prueba que mide en silencio a un visitante nuevo. Cuenta las cookies después del lanzamiento en lugar de fiarte del comando, ya que un campo url ausente salta una sin error, y usa una ruta absoluta tras @. Guarda un archivo por identidad junto al perfil de esa identidad y añade url a cada cookie exportada antes de reutilizarla. Anota el origen de los valores, los atributos, la versión del navegador y cómo verificaste el estado precargado, para que la siguiente ejecución varíe en un campo conocido y no en varios a la vez.
Este artículo forma parte de la guía: Superficies de identidad del navegador: panorama de coherencia
Artículos Relacionados
Cookie Store API: cookies asíncronas y privacidad
IdentidadCookies particionadas (CHIPS): privacidad y sesiones seguras
IdentidadFedCM y privacidad del navegador: diseñar flujos de cuenta
IdentidadCookies, localStorage e IndexedDB: dónde debe vivir el estado
IdentidadCredential Management API: diseña un inicio de sesión seguro
Primeros pasosRecetas CLI para automatización de navegador: ejemplos de comandos
Lleva BotBrowser de la investigación a producción
Usa estas guías para entender el modelo y después avanzar hacia validación multiplataforma, contextos aislados y despliegue de navegador preparado para escalar.