返回知识中心
身份

浏览历史预置:会话历史与全局历史

说明预置的浏览历史与脚本化导航、复用用户数据目录、history.pushState 有何不同,以及 history.length 与全局历史数据库有何区别。

BotBrowser Team

文档中心

想直接看维护中的产品文档?

这篇文章对应的主题已经有文档中心页面。需要规范流程、当前参数和长期参考时,优先看 docs。

刚启动的浏览器会话几乎没有过去。标签页的前进后退栈里只有一个条目,地址栏没有任何可建议的内容,历史记录页面也可能是空的。运行可重复浏览器测试的团队有时希望从另一种状态开始:会话已经具有一定的导航深度,这样被测代码在每次运行中遇到的条件都相同。

这个目标容易表述,却容易做错,因为有两种不同的记录都被称为历史。下面先把这两种记录区分开,再比较四种准备会话的方式(脚本化导航、复用用户数据目录、history.pushState,以及用 BotBrowser 在启动时预置),列出 --bot-inject-random-history 的各种模式,最后给出一个简短流程,说明如何检查结果,而不把一种记录误当成另一种。

两个面板:单个标签页的会话历史由 history.length 读取,全局历史显示在 chrome://history,并提醒两处都要检查。

要点回顾

  • 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 称之为 omnibox)提供数据。它与配置文件数据保存在一起,每个标签页都会向它写入,而页面脚本无法读取它。像 history.length 这样的数字,并不能告诉你其中有什么。

这两种记录回答的是不同的问题。会话历史回答的是这个标签页身后有多少步,全局历史回答的是这个配置文件访问过哪些地址。只读取 history.length 的测试观察的是第一种记录,打开 chrome://history 的测试观察的是第二种。两种观察都无法证明另一种记录的情况,所以向前进后退栈添加条目的做法,并不会自动在历史页面里增加一行,而填充历史页面的做法,也不会自动改变脚本读到的数字。

这对测试很重要,因为应用代码会直接或间接地读取这两类状态。单页应用可能只在 history.length 大于 1 时才显示自己的返回按钮。登录流程可能根据是否存在更早的条目来决定把访问者送到哪里。手工测试的人在配置文件有了一些过去之后,会看到不同的地址栏建议。如果测试环境总是从最短的过去开始,测试就只会走到这类代码的首次访问分支。预置让你可以有意地去运行另一个分支。

会话历史还有一个容易被忘记的特性:它属于单个标签页。弹出窗口、第二个标签页或新打开的窗口都会开始自己的列表。当测试在与已准备的标签页不同的标签页中读取 history.length 时,读到的是另一份列表,新标签页中的值 1 是预期行为,并不表示预置失败。

准备会话的四种方式

在真正的测试开始之前,有四种常见方式可以让会话带上一些历史。它们在触及哪种记录、造成哪些副作用以及可重复程度上各不相同。

脚本化导航

最直接的方法是真的去访问页面。测试脚本在每次导航后记录 HTTP URL、最终重定向目标和 history.length,然后开始真正的工作。这是列表中唯一能产生真实页面状态的方法:访问的地址正是你选择的地址,重定向按站点实际返回的方式被记录,Cookie 和存储由站点自己写入。它也是成本最高的方法。

  • 时间。 每个页面都要加载,所以准备工作需要数秒到数分钟,取决于页面和连接情况。
  • 网络流量。 每次访问都会发出真实请求,占用浏览器所走路由的带宽,还可能触发速率限制。
  • 副作用。 页面会在测试开始前运行自己的脚本、设置 Cookie 并发送分析事件,测试因此不再从干净状态开始。
  • 差异。 加载时间、重定向和动态内容在每次运行中都不同,因此得到的历史每次也不一样。

当测试依赖特定的已访问地址或真实页面状态时,使用脚本化导航。列表中的其他方法都无法提供这两者。

复用用户数据目录

让每次启动都指向同一个 --user-data-dir,会保留之前运行存储的一切,包括它的全局历史数据库。代价是其他内容也一并保留:Cookie、本地存储、缓存文件和 Service Worker。你无法只带走历史而重置其余部分。

测试也因此不再严格可重复,因为起始状态取决于上一次运行留下了什么,第 N 次运行依赖于第 1 到 N-1 次运行。每次运行前从已知快照复制目录可以消除这种漂移,但需要付出文件处理和磁盘空间的代价。这种方式适合真正关注长期状态的测试。要获得一个历史深度,它是一种很重的办法。

调用 history.pushState

页面脚本可以调用 history.pushState(state, '', url),在不加载任何内容的情况下向标签页的会话历史添加一个条目。MDN 指出,新的 URL 必须与当前 URL 同源,所以一个站点上的脚本无法用其他站点的地址填满这个栈,而且 MDN 只在会话历史栈的范围内描述这个调用。

这个调用是为单页应用设计的页面 API,并不是构建浏览记录的方法。在测试中它只有一个用途:重现单页应用扩充自身返回栈的方式,以便检查你的代码在 history.length 不经页面加载而增长时如何反应。

在启动时预置

BotBrowser 的 --bot-inject-random-history 标志(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本次运行使用的浏览器构建串
firstPageLength第一页导航前的 history.length
afterOneNavigation恰好导航一次之后的同一取值
historyPagechrome://history 是否列出条目,以及形式

五个字段足以把真实变化和读错的运行区分开,但前提是每次都把五个都写下来。只记长度不记构建串的记录,无法区分「种子变了」和「浏览器变了」,正是这种模糊让回归报告无法处理。

读记录要逐字段比较两次运行,而不是孤立地比较两个数字。当只有 build 不同而 firstPageLength 随之变化时,说明种子仍然生效,是构建改变了偏移量。当只有 flag 不同时,如果两次运行都使用精确数量,偏移量应当正好按预置数量的差值移动。当两个不同的精确数量下 firstPageLength 完全不变时,说明 flag 没有送达进程,记录剩下的部分只是把同一次启动描述了两遍;随机模式下的两次运行在文档所述区间内完全可能合法地落在同一长度,所以那里的不变值什么也证明不了。

导航字段专门用来抓差一错误。第一页报告的是预置数量加一,所以当记录里 afterOneNavigation 等于 firstPageLength 时,它描述的那次导航从未提交,通常是因为页面重定向或替换了自己的条目。这是关于站点的发现,不是关于种子的。

把 historyPage 留作观察而不是断言。文档没有描述历史页面对预置条目会显示什么,所以把看到的内容连同构建串记下来,让下一个人去比较。一条断言预期历史页状态的记录,会把文档从未作出的承诺写进代码。

实用结论

BotBrowser 的适用范围与边界。 预置历史只是测试计划中的一小部分。它固定了一个变量,即会话在启动时的深度,从而让其他变量可以在稳定的背景下进行研究。

BotBrowser(PRO)支持在启动时通过 --bot-inject-random-history 预置合成的浏览历史,可以是随机数量的条目,也可以是精确数量,让你无需脚本化页面加载,就能让每个测试会话从可重复的历史深度开始。它不能让你指定被注入的 URL,不能保证这些条目匹配某个地区或在任何给定站点看来像真实使用,也不能替代在需要特定已访问 URL 或真实页面状态时的脚本化导航。

在发布中依赖它们之前,请查看当前文档中的级别、数量范围和 history.length 的措辞,并以 MDN 的 history.length 页面作为行为中会话部分的参考。

公开来源

#History#Injection#Identity#Browsing#隐私

本文属于指南: 浏览器身份表面:一致性实用总览

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。