← 全部文章

教程

为 QA 强制指定某个 A/B 测试或功能开关变体

A/B 测试和功能开关对产品团队很好用,对 QA 却很折磨。你被随机分桶一次,之后就再也看不到另一个变体了。本文介绍如何夺回控制权:找到网站用来给你分桶的那个值,在页面读取它之前覆盖掉它,再用一条 JustZix 规则按 URL 模式固定某个变体。

网站如何给你分桶

几乎所有客户端实验工具 —— Optimizely、GrowthBook、LaunchDarkly、Statsig、Split,以及自研方案 —— 工作原理都一样。你第一次访问时它掷一次骰子,把结果写到某个持久化的地方,之后每次访问都从同一个地方读取,让你的体验保持稳定。这个「某个持久化的地方」几乎总是以下三者之一:

前两者你可以直接覆盖。第三种更难 —— 下文细说。

找到分桶用的键

打开 DevTools,进入 Application 标签。在 Storage 下,Cookies 和 Local Storage 并排展示。重新加载页面,扫一眼有没有像实验的东西:含有 abexpvariantflagbuckettestfeature 的键,或者像 optimizelygrowthbook 这样的厂商名。

如果没有明显的东西,那么决策是通过网络送达的。打开 Network 标签,筛选到 Fetch/XHR,重新加载,找像 /api/flags/decideconfig.json 这样的请求。响应 JSON 会告诉你变体名 —— 关键的是 —— SDK 把它们存在哪个键下。

在脚本读取之前覆盖 localStorage

时机就是一切。实验 SDK 很早就读取它的键,通常在 DOMContentLoaded 之前。你的 JustZix JS 规则必须在那次读取之前运行。把规则设为在 document_start 运行,并立即写入值:

// Pin the GrowthBook-style variant before the SDK initialises
const KEY = 'gb_anonymous_id';      // the bucketing key you found
const FORCED = 'qa-pinned-variant-b';

// Overwrite whatever value the SDK would have rolled
localStorage.setItem(KEY, FORCED);

// Some SDKs cache a whole decision object — pin that too
localStorage.setItem('growthbook_features', JSON.stringify({
  'new-checkout': { defaultValue: true },
  'pricing-table-v2': { defaultValue: 'variant-b' }
}));

对匿名 ID 这种方式,挑一个稳定、非随机的 ID:大多数 SDK 会把那个 ID 哈希成分桶,所以同一个字符串总会落到同一个变体。试几个 ID,记下哪一个给你变体 B,然后永远复用它。

覆盖一个 cookie

Cookie 更简单 —— 中间没有 SDK,只是一个字符串。在 document_start 写入它,让页面自己的脚本看到你的值:

// Force the experiment cookie for this domain
function setCookie(name, value) {
  document.cookie =
    name + '=' + value + '; path=/; max-age=31536000; SameSite=Lax';
}

setCookie('ab_variant', 'B');
setCookie('feature_new_nav', 'on');

如果 cookie 是 HttpOnly 的,你根本无法从 JavaScript 触碰它 —— 那是一个服务端开关,下面会讲。你可以在 Application 标签里确认这一点:HttpOnly cookie 会在那一列显示一个勾。

处理服务端开关

当变体由服务端决定时,你收到的 HTML 已经是那个变体了。没有客户端的值可以翻转。你有两个诚实的选项:

查询参数覆盖是最干净的路径。如果你的站点支持 ?ff_override=new-checkout:true,你甚至不需要 JavaScript —— 把那个 URL 加书签即可。

按 URL 模式固定一个变体

JustZix 真正的胜出之处在于作用域控制。你不希望每个站点都被同样分桶 —— 你希望这个预发布环境固定到变体 B,而那个保持不动。创建一条带紧凑 URL 模式的规则:

URL pattern:  https://staging.example.com/*
JS (document_start):
  localStorage.setItem('exp_checkout', 'variant-b');
  document.cookie = 'ab_force=B; path=/';

每个变体做一条规则 —— 「固定结账 A」、「固定结账 B」、「固定对照组」 —— 然后从动作栏切换它们。把全部三条分支跑一遍 QA 就变成三次点击,而不是三个隐身窗口外加大量运气。

验证变体确实生效了

不要因为页面看起来不同了就相信覆盖生效了。要确认它。大多数 SDK 会在某处暴露当前的分配 —— 从 Output Console 把它打印出来:

// Read back what the SDK decided, after it initialised
window.addEventListener('load', () => {
  // GrowthBook example
  if (window.growthbook) {
    console.log('Active features:', window.growthbook.getFeatures());
  }
  // Generic: dump every storage key for the audit trail
  console.log('Forced storage:', { ...localStorage });
});

把它和 Output Console 窗口搭配使用,这样在你点击走完流程时,分配在标签页内是可见的。如果 SDK 报告的是你强制的变体,你的测试就是真实的。如果它报告的是别的东西,说明你的规则跑得太晚 —— 把它推到 document_start

常见陷阱

另见

别再依赖隐身窗口和好运掷骰了。安装 JustZix,为每个变体建一条规则,按需演练每个实验的每条分支。

为这篇文章评分

暂无评分 — 成为第一个。

自己动手试试

安装 JustZix,粘贴本文中的任意代码片段。两分钟,从零到一条在你所有设备上生效的规则。

获取 JustZix

功能 · 工作原理 · 示例 · 应用场景