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

Предзаполнение истории: история сеанса и глобальная

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

BotBrowser Team

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

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

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

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

Эту цель легко сформулировать и легко реализовать неверно, потому что историей называют две разные записи. Ниже эти две записи разделены, сравниваются четыре способа подготовить сеанс (навигация скриптами, повторно используемый каталог данных, history.pushState и заполнение при запуске в BotBrowser), перечислены режимы --bot-inject-random-history и дана короткая процедура, которая позволяет проверить результат, не принимая одну запись за другую.

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

Кратко

  • history.length считает записи сеанса одной вкладки; он не открывает список всего профиля, показанный в chrome://history.
  • Навигация скриптами создаёт выбранные URL, перенаправления, cookie и хранилище. history.pushState() меняет только стек сеанса текущей вкладки.
  • --bot-inject-random-history=<count> задаёт глубину сеанса при запуске для воспроизводимой проверки ветки, но не выбирает посещённые URL и не создаёт состояние сайта.

Содержание руководства

Две истории за одним словом

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

Первая запись это история сеанса. Каждая вкладка хранит упорядоченный список показанных страниц, и кнопки «назад» и «вперёд» перемещаются по этому списку. Скрипты страницы получают к нему доступ через интерфейс History. По описанию MDN, history.length это целое число только для чтения, равное количеству записей в истории сеанса, включая текущую загруженную страницу, а страница, открытая в новой вкладке, сообщает 1. Вызовы history.back(), history.forward() и history.go() перемещаются по списку, а history.pushState() и history.replaceState() изменяют его без загрузки нового документа.

Вторая запись это глобальная история. Это список посещённых адресов для всего браузера, который питает страницу chrome://history и подсказки в адресной строке, называемой в Chromium омнибоксом. Она хранится вместе с данными профиля, в неё пишет каждая вкладка, а скрипт страницы не может её прочитать. Число вроде history.length ничего не говорит о её содержимом.

Две записи отвечают на разные вопросы. История сеанса отвечает, сколько шагов у этой вкладки позади. Глобальная история отвечает, какие адреса посещал этот профиль. Тест, который читает только history.length, наблюдает первую запись, а тест, который открывает chrome://history, наблюдает вторую. Ни одно из наблюдений ничего не доказывает о другой записи, поэтому приём, который добавляет запись в стек «назад и вперёд», не добавляет автоматически строку на страницу истории, а приём, который заполняет страницу истории, не меняет автоматически число, которое читает скрипт.

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

Об ещё одном свойстве истории сеанса легко забыть: она принадлежит одной вкладке. Всплывающее окно, вторая вкладка или только что открытое окно начинают собственный список. Когда тест читает history.length в вкладке, отличной от подготовленной, он читает другой список, а значение 1 в новой вкладке это ожидаемое поведение, а не признак того, что предзаполнение не сработало.

Четыре способа подготовить сеанс

Есть четыре распространённых способа дать сеансу какую-то историю до начала настоящего теста. Они различаются тем, какую запись затрагивают, какие побочные эффекты вызывают и насколько воспроизводимы.

Навигация скриптами

Самый прямой способ состоит в том, чтобы посещать страницы. Скрипт записывает HTTP URL, конечную цель каждого перенаправления и history.length после каждой навигации, затем приступает к настоящей работе. Это единственный способ в списке, который создаёт реальное состояние страниц: посещённые адреса ровно те, что вы выбрали, перенаправления записываются так, как их отдал сайт, а cookie и хранилище заполняют сами сайты. Это также самый затратный способ.

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

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

Повторное использование каталога пользовательских данных

Если направлять каждый запуск в один и тот же --user-data-dir, сохраняется всё, что записал предыдущий запуск, включая его базу глобальной истории. Цена в том, что сохраняется и всё остальное: cookie, локальное хранилище, файлы кеша и сервис-воркеры. Нельзя перенести только историю и сбросить остальное.

Тест перестаёт быть строго воспроизводимым, потому что начальное состояние это то, что оставил последний запуск. Запуск N тогда зависит от запусков с 1 по N-1. Копирование каталога из известного снимка перед каждым запуском убирает дрейф, но требует работы с файлами и места на диске. Такой подход подходит тестам, которые действительно посвящены долгоживущему состоянию. Для получения глубины истории это тяжёлый способ.

Вызов history.pushState

Скрипт страницы может вызвать history.pushState(state, '', url), чтобы добавить запись в историю сеанса вкладки, ничего не загружая. MDN отмечает, что новый URL должен иметь то же происхождение, что и текущий, поэтому скрипт одного сайта не может наполнить стек адресами других сайтов, а сам вызов MDN описывает только в терминах стека истории сеанса.

Этот вызов является API страницы для одностраничных приложений. Это не способ построить журнал посещений. В тесте он полезен для одной цели: воспроизвести то, как одностраничное приложение наращивает собственный стек «назад и вперёд», чтобы проверить реакцию вашего кода, когда history.length растёт без какой-либо загрузки страницы.

Заполнение при запуске

Флаг --bot-inject-random-history в BotBrowser (уровень PRO) идёт другим путём. Вместо посещения страниц он создаёт синтетические записи при старте, до загрузки первой страницы. Поскольку записи генерируются при старте, а не загружаются с сайтов, для подготовки не нужны скриптовые загрузки страниц. Поскольку количество задаётся флагом, два запуска с одним и тем же значением стартуют с одной и той же глубины.

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

Режимы флага заполнения

В документации BotBrowser по заполнению истории перечислены два способа включить флаг и один способ его выключить.

  • --bot-inject-random-history или --bot-inject-random-history=true это случайный режим. Он внедряет случайное число записей от 2 до 7, что, по документации, даёт history.length от 3 до 8.
  • --bot-inject-random-history=15 это точный режим. Он внедряет ровно указанное число записей, поэтому history.length равна количеству плюс один: 15 записей дают 16 на первой странице. В документации сказано, что значение должно быть от 1 до 25.
  • --bot-inject-random-history=false отключает внедрение.

Ту же настройку можно задать в конфигурации профиля ключом injectRandomHistory, где true выбирает случайный режим, а число выбирает точный. Если заданы оба варианта, флаг командной строки переопределяет профиль.

# Случайный режим: от 2 до 7 записей
chromium-browser --bot-profile="path/to/profile.enc" --bot-inject-random-history
# Точный режим: 15 записей, на первой странице history.length равна 16
chromium-browser --bot-profile="path/to/profile.enc" --bot-inject-random-history=15

Два свойства точного режима важны для воспроизводимости. Арифметика достаточно проста, чтобы проверять её в тесте: ожидаемое значение на первой странице равно количеству плюс один. А случайный режим даёт диапазон, поэтому проверка должна принимать от 3 до 8, а не одно число. Если тесту нужно одно значение, используйте точный режим и запишите количество в команду запуска.

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

Флаг сочетается с другими параметрами заполнения из документации, например --bot-cookies и --bot-bookmarks. О них см. Управление cookie и Внедрение закладок, а об организации наборов профилей см. Управление профилями. Для нескольких контекстов браузера в одном браузере документация относит историю по контекстам к функциям ENT Tier3.

Как выбрать подход для тестового запуска

Выбирайте подход по вопросу, на который отвечает тест. Таблица кратко показывает, что затрагивает каждый способ и чего он стоит.

ПодходЧто затрагиваетСеть и побочные эффектыВоспроизводимостьВыбор URL
Навигация скриптамиИстория сеанса и глобальная историяНастоящие запросы, скрипты страниц, cookieМеняется со временем загрузки и содержимымДа
Повторно используемый каталог данныхВсё, что сохранено ранееНовых запросов нет, но возвращается старое состояниеЗависит от предыдущих запусковТолько через предыдущие запуски
history.pushStateИстория сеанса одной вкладкиНетВысокаяТолько то же происхождение
Флаг заполненияЗаписи, учитываемые history.length, по документацииГенерируются при старте, не загружаются с сайтовВысокая в точном режимеНет

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

  • Нужен стек «назад и вперёд» известной глубины. Используйте точный режим. Ожидаемое history.length равно количеству плюс один, а одна и та же команда запуска каждый раз даёт одну и ту же отправную точку.
  • Нужна некоторая глубина, но не конкретное число. Используйте случайный режим и сделайте так, чтобы проверки принимали диапазон от 3 до 8.
  • Нужны конкретные адреса или реальное состояние страниц. Используйте навигацию скриптами. Страницу, которая читает cookie, установленный более ранним визитом, или сайт, который ведёт счёт посещений, можно подготовить только настоящим посещением.
  • Вы проверяете, как одностраничное приложение наращивает свой стек. Вызывайте history.pushState внутри теста, потому что именно это поведение и проверяется.
  • Нужно, чтобы всё состояние переходило между запусками. Используйте скопированный каталог данных и принимайте связанный с этим дрейф.

Подходы можно и сочетать. Распространённая схема состоит в том, чтобы заполнить глубину флагом, а затем скриптами выполнить только две-три навигации, от которых тест действительно зависит. Подготовка остаётся короткой, а состояние страниц реально там, где это важно. При сочетании помните, что каждая навигация скриптом увеличивает счётчик. Ожидайте, что history.length растёт на единицу за каждую новую запись поверх заполненного значения, и измерьте базовый уровень в своей сборке перед написанием точных проверок, потому что перенаправления и скрипты страниц могут добавить записи, которых вы не планировали.

Проверка того, что произошло на самом деле

Проверка должна читать обе записи, потому что каждая говорит о своём. Журнал запуска с перечисленными ниже полями превращает разовое наблюдение в то, что коллега может повторить.

  1. Прочитайте значение сеанса. Откройте страницу и прочитайте history.length в консоли инструментов разработчика. В точном режиме с количеством 15 первая страница сообщает 16, согласно документации. В случайном режиме значение должно попасть в диапазон от 3 до 8.
  2. Откройте chrome://history в том же сеансе и посмотрите записи. Документация описывает влияние на history.length и на сеанс. Страницу истории она не описывает, поэтому появление и вид записей там нужно наблюдать в своей сборке и записывать, а не предполагать.
  3. Выполните один переход и прочитайте значение снова. Оно должно вырасти на единицу из-за новой записи. Если этого не произошло, проверьте, не выполнила ли страница перенаправление или не заменила ли свою запись.
  4. Запустите снова с теми же флагами и повторите проверки. Заполненные записи применяются к каждому новому сеансу, поэтому точный режим должен давать те же значения, а случайный режим должен оставаться в своём диапазоне.
  5. Запишите значение запуска рядом с результатом. Зафиксируйте флаг, сборку, ожидаемую длину, наблюдаемую длину и то, что показала страница истории.

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

Большинство неудачных проверок происходит из короткого списка ошибок. Первая: чтение history.length в вкладке, отличной от подготовленной, где новая вкладка корректно сообщает 1. Вторая: сравнение заполненного значения с числом, измеренным после дополнительных переходов. Третья: ожидание, что записи переживут перезапуск, чего, по документации, не происходит. Четвёртая: точная проверка для случайного режима.

Если history.length остаётся равной 1, документация указывает на три причины. Флаг мог быть передан вне массива аргументов запускающего кода. Лицензия могла быть неактивной, и тогда вывод консоли BotBrowser сообщает об ошибке лицензии. Либо количество было записано без знака равенства или вне допустимого диапазона от 1 до 25.

Совпадающее значение history.length показывает лишь то, что у сеанса есть записи. Оно не показывает, как конкретный сайт отнесётся к сеансу, и такого утверждения здесь не делается. Предзаполненная история это один из входов тестового сеанса наряду с cookie, закладками, регионом и часовым поясом, которые вы настраиваете отдельно.

Одна запись на запуск, и что означает diff

Поле записиЗачем оно нужно
flagТочное значение --bot-inject-random-history или exact
buildСтрока сборки браузера, использованная в запуске
firstPageLengthhistory.length на первой странице до перехода
afterOneNavigationТо же значение ровно после одного перехода
historyPageПоказала ли chrome://history записи и в каком виде

Пяти полей достаточно, чтобы отличить реальное изменение от неверно прочитанного запуска, но только если все пять записываются каждый раз. Запись с длиной и без сборки не различает «изменилось зерно» и «изменился браузер», и именно эта неоднозначность делает отчёт о регрессии непригодным к работе.

Читайте запись, сравнивая два запуска поле за полем, а не два числа по отдельности. Когда различается только build и firstPageLength следует за ним, зерно применилось, а сборка сдвинула смещение. Когда различается только flag, смещение должно сдвинуться ровно на разницу в заданном количестве, если оба запуска используют точное количество. Когда firstPageLength не меняется между двумя разными точными количествами, флаг не дошёл до процесса, и остальная запись дважды описывает один и тот же запуск; два запуска в случайном режиме могут законно совпасть по длине внутри документированного диапазона, поэтому неизменное значение здесь ничего не доказывает.

Поле перехода ловит ошибку на единицу. Первая страница сообщает заданное количество плюс один, поэтому запись, где afterOneNavigation равно firstPageLength, описывает переход, который так и не состоялся, обычно потому что страница перенаправила или заменила собственную запись. Это находка о сайте, а не о зерне.

Оставляйте historyPage наблюдением, а не утверждением. Документация не описывает, что страница истории показывает для заданных записей, поэтому записывайте увиденное вместе со сборкой и дайте следующему читателю сравнить. Запись, утверждающая ожидаемое состояние страницы истории, закодировала бы обещание, которого документация не давала.

Практическое заключение

Где BotBrowser полезен и где его границы. Предзаполненная история это небольшая часть плана тестирования. Она фиксирует одну переменную, глубину сеанса при запуске, чтобы остальные переменные можно было изучать на стабильном фоне.

BotBrowser (PRO) поддерживает заполнение синтетической историей просмотров при запуске через --bot-inject-random-history: случайным числом записей или точным их количеством, чтобы вы могли начинать каждый тестовый сеанс с воспроизводимой глубины истории без загрузки страниц скриптами. Он не позволяет выбирать внедряемые URL, не может гарантировать, что записи соответствуют региону или выглядят как реальное использование для конкретного сайта, и не заменяет навигацию скриптами, когда нужны конкретные посещённые URL или реальное состояние страницы.

Перед тем как опираться на уровень, диапазон количеств и формулировку про history.length в выпуске, сверьтесь с актуальной документацией, а страницу MDN о history.length используйте как справку по сеансовой части поведения.

Открытые источники

#History#Injection#Identity#Browsing#приватность

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

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

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