← All posts

Tutorials

Force a specific A/B test or feature-flag variant for QA

A/B tests and feature flags are great for product teams and miserable for QA. You get bucketed once, randomly, and then you can never see the other variant again. This article shows how to take control: find the value the site uses to bucket you, override it before the page reads it, and pin a variant per URL pattern with a JustZix rule.

How sites bucket you

Almost every client-side experimentation tool — Optimizely, GrowthBook, LaunchDarkly, Statsig, Split, homegrown setups — works the same way. On your first visit it rolls a dice, writes the result somewhere persistent, and reads that same place on every later visit so your experience stays stable. The "somewhere persistent" is almost always one of three things:

The first two you can override directly. The third is harder — more on that below.

Finding the bucketing key

Open DevTools and go to the Application tab. Under Storage you have Cookies and Local Storage side by side. Reload the page and scan for anything that looks like an experiment: keys containing ab, exp, variant, flag, bucket, test, feature, or a vendor name like optimizely or growthbook.

If nothing obvious shows up, the decision arrives over the network. Open the Network tab, filter to Fetch/XHR, reload, and look for a request to something like /api/flags, /decide, or config.json. The response JSON tells you the variant names and — crucially — which key the SDK stores them under.

Overriding localStorage before scripts read it

Timing is everything. The experimentation SDK reads its key early, often before DOMContentLoaded. Your JustZix JS rule must run before that read. Set the rule to run at document_start and write the value immediately:

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

Pick a stable, non-random ID for the anonymous ID approach: most SDKs hash that ID into a bucket, so the same string always lands in the same variant. Roll through a few IDs once, note which one gives you variant B, and reuse it forever.

Overriding a cookie

Cookies are simpler — there is no SDK in the way, just a string. Write it at document_start so the page's own scripts see your value:

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

If the cookie is HttpOnly you cannot touch it from JavaScript at all — that is a server-side flag, covered next. You can confirm this in the Application tab: HttpOnly cookies show a checkmark in that column.

Handling server-side flags

When the variant is decided on the server, the HTML you receive is already the variant. There is no client-side value to flip. You have two honest options:

A query-parameter override is the cleanest path. If your site supports ?ff_override=new-checkout:true, you do not even need JavaScript — just bookmark the URL.

Pinning a variant per URL pattern

The real win with JustZix is scoping. You do not want every site bucketed the same way — you want this staging environment pinned to variant B and that one left alone. Create a rule with a tight URL pattern:

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

Make one rule per variant — "Pin checkout A", "Pin checkout B", "Pin control" — and toggle them from the action bar. A QA pass through all three branches becomes three clicks instead of three incognito windows and a lot of luck.

Verifying the variant actually took

Do not trust that the override worked just because the page looks different. Confirm it. Most SDKs expose the active assignment somewhere — log it from the 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 });
});

Pair this with the Output Console window so the assignment is visible in-tab while you click through the flow. If the SDK reports the variant you forced, your test is real. If it reports something else, your rule ran too late — push it to document_start.

Common pitfalls

See also

Stop relying on incognito windows and lucky dice rolls. Install JustZix, build one rule per variant, and exercise every branch of every experiment on demand.

Rate this post

No ratings yet — be the first.

Try it yourself

Install JustZix and paste any snippet from this article. Two minutes from zero to a working rule across all your devices.

Get JustZix

Features · How it works · Examples · Use cases