Назад в Центр знаний
Идентичность

Управление cookies в браузере при работе с несколькими идентичностями

Как cookies хранят состояние входа и согласия, почему добавленные после создания страницы могут не попасть в первый запрос и как загружать отдельный набор для каждой идентичности.

BotBrowser Team

Документация

Нужна поддерживаемая продуктовая документация?

У этой статьи есть соответствующая страница в центре документации. Используйте docs для каноничного сценария настройки, актуальных флагов и долгосрочной справки.

Cookies это небольшие фрагменты состояния, которые сервер просит браузер сохранить и вернуть вместе с последующими запросами. Сайты используют их для сеансов входа, для настроек языка и темы и для записи выбора согласия. Если вы тестируете сайт как разработчик или QA-инженер, хранилище cookies становится частью входных данных теста. Одна и та же страница ведёт себя по-разному для нового посетителя, для вернувшегося посетителя, который уже принял баннер согласия, и для вошедшего в систему аккаунта. Тест, который не контролирует свой начальный набор cookies, невольно измеряет один из этих случаев.

Разделы ниже прослеживают cookie от заголовка сервера до хранилища браузера, объясняют, почему cookies, добавленные через API фреймворка, могут появиться после первого запроса, и показывают, как набор cookies, загруженный при запуске флагом --bot-cookies в BotBrowser, даёт каждой идентичности документированное и воспроизводимое начальное состояние. Они также называют то, что хранилище cookies не изолирует, потому что отдельный файл cookies это лишь один слой отдельной идентичности. О том, как несколько аккаунтов разделяются не только на уровне cookies, см. Несколько аккаунтов в соцсетях с изоляцией браузера.

Две временные шкалы. При загрузке на запуске cookies лежат в хранилище до создания страницы, и первый запрос их несёт. При вызове фреймворка, сделанном после начала загрузки страницы, первый запрос уходит без cookies, и только следующие запросы их несут.

Схема сравнивает два порядка событий. Когда cookies лежат в хранилище до появления любой страницы, первый запрос их несёт. Когда скрипт добавляет их после того, как страница уже начала загружаться, более ранний запрос уходит без них.

Кратко

  • Cookie отправляется только тогда, когда Domain, Path, Secure, SameSite и срок действия совпадают с запросом, поэтому неверные атрибуты выглядят как сайт, игнорирующий вашу настройку.
  • context.addCookies() и page.setCookie() выполняются уже после создания контекста или страницы, поэтому запрос, ушедший раньше, не несёт ни одной cookie.
  • --bot-cookies загружает набор до первой навигации, из встроенного JSON или файла с @, а cookie без поля url отбрасывается без сообщения.
  • Один файл cookies на идентичность рядом с её собственным профилем привязывает тесты вернувшегося посетителя и согласия к документированному состоянию.

Содержание

Механизм определяет RFC 6265. Сервер отправляет заголовок ответа Set-Cookie с именем, значением и необязательными атрибутами. Браузер сохраняет cookie и в последующих запросах к подходящему хосту и пути возвращает её в заголовке запроса Cookie. Скрипты страницы могут читать и записывать cookies через document.cookie, кроме помеченных HttpOnly, которые браузер держит вне доступа скриптов страницы. Руководство MDN по cookies описывает тот же процесс и добавляет атрибут SameSite, появившийся после исходной спецификации.

На отправку влияют пять групп атрибутов: Domain, Path, срок действия, Secure и SameSite. HttpOnly не влияет на отправку, а только определяет, могут ли скрипты страницы читать cookie.

  • Domain и cookies только для хоста. Cookie, заданная без атрибута Domain, принадлежит ровно тому хосту, который её задал. Cookie с Domain=example.com отправляется также на поддомены этого хоста.
  • Path. Cookie отправляется только для путей запроса, совпадающих с сохранённым путём.
  • Expires и Max-Age. Cookie без обоих атрибутов является сеансовой и должна закончиться вместе с сеансом просмотра. Cookie с будущей датой постоянна и переживает перезапуск.
  • Secure. Cookie отправляется только по HTTPS.
  • SameSite. Значения Strict, Lax и None определяют, сопровождает ли cookie межсайтовые запросы, а None используется вместе с Secure.

Для теста атрибуты часто важнее значения. Cookie, сохранённая с неверным доменом, неверным путём или с Secure на тестовом сайте по обычному HTTP, сохраняется без ошибки и потом никогда не отправляется, что выглядит так, будто сайт игнорирует вашу настройку. RFC 6265 задаёт лишь минимальные пределы количества cookies, которые хранит браузер, и оставляет вытеснение на усмотрение реализации, а браузеры со временем меняли значения по умолчанию для таких атрибутов, как SameSite. Опирайтесь и на спецификацию, и на руководство MDN, а поведение проверяйте в той версии браузера, с которой вы действительно тестируете.

Cookies согласия требуют ещё одного замечания. Стандартного имени или значения для выбора согласия не существует. Каждый сайт или платформа согласия определяет собственную cookie, собственный формат значения и собственный срок жизни, а имена и значения в примерах ниже условные. Чтобы построить реалистичное начальное состояние, сделайте выбор один раз в обычном сеансе браузера, который вы контролируете, прочитайте cookie, которую сайт задаёт в ответ, и скопируйте её имя, формат значения, домен, путь и срок действия. Cookie, придуманную наугад, сайт может просто проигнорировать, и тогда тест покажет баннер, который вы считали закрытым.

Почему cookies, добавленные через API фреймворка, могут не попасть в первый запрос

Фреймворки автоматизации дают собственные вызовы для cookies. У Playwright это context.addCookies(), у Puppeteer page.setCookie(). Оба вызова ваш скрипт делает уже после того, как контекст браузера или страница существуют. Браузер запускается, создаются контекст и страница, и только потом приходит ваш вызов. С этого момента cookies лежат в хранилище. Запрос, отправленный до вызова, не несёт ни одной из них.

Во многих скриптах этот разрыв безвреден. Если вы создаёте контекст, добавляете cookies и лишь затем впервые вызываете goto(), первая навигация уже несёт их. Разрыв проявляется, когда что-то загружается раньше вашего вызова. Страница, которую браузер или средство запуска открывает при старте, может начать загрузку сама. Фикстура тестового раннера может создать страницу в одном месте, а добавить cookies в другом. Общая вспомогательная функция может открыть первую страницу за вас. В каждом случае самый ранний запрос уходит, пока хранилище cookies ещё пусто, а то, какой порядок вы получите, может зависеть от тайминга, который вы не контролируете.

Тесты согласия и вернувшегося посетителя чувствуют это сильнее всего. Первый ответ часто решает, покажет ли сервер баннер согласия или выдаст новую сеансовую cookie посетителю, которого никогда не видел. Если этот ответ формируется для запроса без cookies, сервер может задать собственную cookie, а cookies, которые вы добавите мгновением позже, могут с ней конфликтовать или прийти слишком поздно. Тогда тест сообщает о том, что случилось с новым посетителем, хотя замысел состоял в проверке вернувшегося.

Загрузка при запуске меняет порядок. По документации BotBrowser, cookies, переданные через --bot-cookies, загружаются до первой навигации страницы, поэтому первый HTTP-запрос уже включает их. Это единственное сравнение, которое здесь проводится: момент, когда cookies становятся доступны, относительно первого запроса. API фреймворков остаются полезными для изменения cookies посреди теста, и заменять их для этого не нужно.

Чтобы выяснить, затрагивает ли разрыв вашу конфигурацию, записывайте только наличие заголовка Cookie и имена в нём с момента запуска браузера. Сравнивайте первые записи с ожидаемыми именами; никогда не сохраняйте исходный заголовок, поскольку он может содержать учётные данные сеанса. Если первый запрос уже несёт ожидаемые имена, порядок правильный, и вызова фреймворка достаточно. Если в самых ранних записях нет имён, перенесите этот набор cookies во флаг запуска.

Загрузка набора cookies для каждой идентичности при запуске

Флаг --bot-cookies требует уровня PRO. Он принимает JSON-массив объектов cookie либо прямо в значении флага, либо из файла, если значение начинается с @. Встроенный JSON подходит для небольшого набора, например одной cookie согласия:

chromium-browser \
  --bot-profile="path/to/profile.enc" \
  --bot-cookies='[{"url":"https://example.com","name":"consent","value":"accepted","domain":".example.com"}]'

Файл подходит для большого набора и сохраняет команду запуска читаемой. Совет документации по устранению неполадок: использовать абсолютный путь после @:

[
  {
    "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": "/"
  }
]

Каждый объект cookie поддерживает такие поля, как они перечислены в документации:

  • url (обязательно): полный URL, используемый для установки cookie, например https://example.com. Cookie без него молча пропускается.
  • name и value (обязательно): сами данные cookie.
  • domain: домен, которому принадлежит cookie. Ведущая точка, как в .example.com, включает поддомены.
  • path: по умолчанию /.
  • secure: по умолчанию true, поэтому cookie отправляется только по HTTPS.
  • httpOnly: по умолчанию false.
  • sameSite: strict, lax или none.
  • expirationDate: метка времени Unix в секундах с начала эпохи.

Большая часть путаницы связана с тремя деталями. Первая это поле url. Cookie без него пропускается молча, поэтому одно отсутствующее поле не вызывает ошибки и даёт набор cookies меньше ожидаемого. Пересчитайте cookies после запуска, а не считайте, что они загрузились. Вторая это secure, по умолчанию равное true. По нашему прочтению этого значения по умолчанию, если ваш тестовый сайт работает по обычному HTTP, явно задайте false, иначе cookie сохранится и никогда не будет отправлена. Третья это способ построения флага в коде. Передавайте в качестве значения результат JSON.stringify() без дополнительных кавычек, потому что лишние кавычки становятся частью значения, и JSON перестаёт разбираться. Используйте также будущий expirationDate в секундах, а не в миллисекундах, чтобы cookie не истекла в момент загрузки.

Запуск Playwright с одной идентичностью, одним профилем и одним файлом cookies выглядит так:

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/');

Как держать идентичности и контексты раздельно

Держите один файл cookies на идентичность и называйте его по имени идентичности. Сопоставляйте его с собственным профилем этой идентичности и не копируйте файл из одной идентичности в другую, потому что скопированный набор переносит выбор согласия и записи входа первой идентичности во вторую. Когда вы меняете профиль, пересматривайте файл cookies одновременно, так как запись согласия или языковая настройка, подходившая старому профилю, может не подойти новому. Хранение файлов в одной документированной папке с доступом только для тех, кто запускает тесты, также даёт проверяющему нечто конкретное для сравнения между запусками, и в этом смысл воспроизводимого начального состояния. Смешивание файлов затрудняет определение причин: если тест согласия и тест с входом в аккаунт используют один файл, сбой может происходить от любого из состояний, и без повторного запуска обоих никто не скажет, от какого. Раздельные файлы превращают этот вопрос в простую справку. Руководство по изоляции нескольких аккаунтов и руководство по управлению профилями объясняют, как организовать профили и сопровождающие их файлы.

Область действия флага зависит от способа передачи. Документация BotBrowser сообщает, что cookies, загруженные при запуске, становятся начальным состоянием браузера. В Puppeteer используйте browser.defaultBrowserContext(), чтобы добраться до этого контекста, так как context.cookies() в другом контексте может вернуть пустой список. С Per-Context Fingerprint флаг --bot-cookies можно вместо этого передать через botbrowserFlags при создании BrowserContext, и тогда cookies импортируются только в этот контекст. Каждый контекст стартует со своим состоянием cookies, разделяя один процесс браузера:

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],
});

Создавайте страницы только после появления контекста, чтобы первая навигация уже использовала состояние cookies этого контекста.

Одно хранилище cookies не изолирует всё, что делает две идентичности независимыми. RFC 6265 прямо называет несколько ограничений, а остальные следуют из того, что документирует флаг:

  • Cookies не изолируются по порту, поэтому два сервиса на одном хосте делят cookies этого хоста.
  • Cookie родительского домена видна всем поддоменам, а соседний поддомен может задать cookie, которую прочитает другой.
  • Локальное хранилище, IndexedDB, кэши и сервис-воркеры это отдельные хранилища. Документация --bot-cookies не описывает их предзагрузку.
  • Файл cookies ничего не говорит о профиле отпечатка, языковых настройках и сетевом маршруте, которые сопровождают идентичность.

Руководство по хранилищам браузера объясняет, чем эти другие хранилища отличаются от cookies. Если идентичность должна стартовать без остаточного состояния, проверьте также, что несёт с собой повторно используемый --user-data-dir, ведь он хранит все эти хранилища вместе, а не только cookies.

Проверка того, что начальное состояние такое, как вы задумали

Три проверки охватывают начальное состояние: что лежит в хранилище, что несёт первый запрос и что может прочитать страница. Этот пример Playwright читает сохранённое состояние, а следующая проверка анализирует самый ранний запрос:

await page.goto('https://example.com/');
console.log(await page.context().cookies('https://example.com/'));

Сравнение имён cookies без записи значений

Наличие заголовка Cookie является только первой проверкой. Для повторяемого результата сравните имена, которые должны были загрузиться, с именами в первом запросе и не сохраняйте значения в журнале:

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(),
  })
);

Пустой список missing доказывает, что названные cookies попали в первый запрос, но не подтверждает корректность их значений или политики сессии сайта. Если имя пропущено, проверьте url, домен, путь, secure, срок действия и порядок запуска до анализа ответа приложения.

Подключайте обработчик запросов до первой навигации, чтобы в него попал самый ранний запрос. Список cookies должен содержать все cookies из вашего файла, а первый запрос должен показывать заголовок cookies. Если число меньше, чем в файле, прежде всего поищите отсутствующее поле url. Помните, что cookie с пометкой HttpOnly не появится в document.cookie. Так определён этот атрибут, поэтому её отсутствие на странице не является сбоем. Для таких cookies проверяйте хранилище и заголовок запроса.

Сравните флаги cookies с тем, что целевой сайт задаёт на самом деле. Откройте сайт обычным способом в одноразовом профиле браузера, посмотрите его заголовки Set-Cookie на панели сети и сопоставьте Domain, Path, Secure, HttpOnly, SameSite и срок действия. Предзагруженная cookie, флаги которой отличаются от настоящей, может обрабатываться иначе, чем cookie, которую выдал бы сайт, и тогда тест охватывает состояние, которого у настоящих посетителей никогда не бывает.

Когда cookie не доходит, разбирайте причины в том порядке, в котором они обычно встречаются. Сначала проверьте поле url, потому что пропущенная cookie не оставляет следов. Затем сравните domain и path с URL запроса, помня, что cookie только для хоста не доходит до поддоменов. В-третьих, посмотрите на secure и схему сайта. В-четвёртых, учтите sameSite, так как cookie с lax или strict может не сопровождать межсайтовый запрос. В-пятых, убедитесь, что срок действия в будущем и задан в секундах. В-шестых, проверьте, что значение флага является корректным JSON без лишних кавычек, а путь к файлу абсолютный. Наконец, в Puppeteer читайте cookies из контекста браузера по умолчанию.

Короткая запись проверки сохраняет конфигурацию воспроизводимой. Для каждой идентичности запишите:

  • источник значений cookies, например состояние согласия, записанное в тестовом аккаунте или задокументированное вручную;
  • файл, назначенный этой идентичности, и профиль, с которым он сопоставлен;
  • срок действия и флаги, сопоставленные с поведением Set-Cookie целевого сайта;
  • как вы проверили предзагруженное состояние и на какой версии браузера.

Если вы экспортируете cookies в конце сеанса, чтобы использовать их повторно, учтите, что объекты cookie, которые возвращает фреймворк, могут не содержать поля url. Добавьте его в каждый объект, прежде чем передавать файл в --bot-cookies, иначе cookie будет пропущена. Загружайте только cookies, принадлежащие вашим собственным тестовым аккаунтам или задокументированным состояниям согласия. Извлечение, подделка или воспроизведение чужих сеансовых cookies выходит за рамки этой конфигурации, а сайт в любом случае может отклонить или сбросить любую внедрённую cookie. Повторяйте проверку при каждом изменении версии браузера, профиля или файла cookies, потому что различие в первом запросе проще объяснить, когда с последнего успешного запуска изменилось что-то одно.

Где BotBrowser применим и где его возможности заканчиваются

BotBrowser документирует загрузку cookies при запуске через --bot-cookies (уровень PRO) из встроенного JSON или файла с @ до первой навигации страницы, так что первый HTTP-запрос уже несёт их, и импорт в один BrowserContext, когда флаг передан через botbrowserFlags, чтобы команды могли воспроизводить документированное начальное состояние cookies для тестов вернувшегося посетителя и сценариев согласия и держать идентичности раздельно. BotBrowser не меняет спецификацию cookies и правила SameSite и Secure, молча пропускает cookies без поля url, не документирует предзагрузку localStorage и IndexedDB и не гарантирует, что целевой сайт примет, сохранит или свяжет с сеансом внедрённую cookie.

Источники

Итоги и практические выводы

Начинайте с запроса, а не с флага: запишите имена самого раннего запроса и сравните их с загруженным файлом, не сохраняя исходные заголовки Cookie. Если запрос несёт ожидаемые имена, порядок верный и достаточно вызова фреймворка; если нет, перенесите набор в --bot-cookies и загружайте его при запуске. Сверяйте Domain, Path, Secure, SameSite и срок действия с заголовками Set-Cookie, которые сайт действительно возвращает, потому что сохранённая с неверными атрибутами cookie никогда не отправляется и даёт тест, молча измеряющий нового посетителя. Считайте cookies после запуска, а не доверяйте команде: отсутствующее поле url отбрасывает одну без ошибки, а после @ нужен абсолютный путь. Держите один файл на идентичность рядом с профилем этой идентичности и добавляйте url каждой экспортированной cookie перед повторным использованием. Записывайте источник значений, атрибуты, версию браузера и то, как вы проверили предзагруженное состояние, чтобы следующий запуск отличался одним известным полем, а не несколькими сразу.

#Cookies#управление#Identity#Session#приватность

Эта статья входит в руководство: Поверхности идентичности браузера: обзор согласованности

Переведите BotBrowser из исследований в продакшн

Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.