Skip to content

AdeptMind/ab-testing

Repository files navigation

@adeptmind/ab-testing

Lightweight A/B experiment bucketing with localStorage persistence. Assign users to experiment groups with a single function call — assignments are sticky, so returning users always see the same variant.

Install

npm install @adeptmind/ab-testing

Quick Start

import { getBucketedValue, parseEnvPct } from "@adeptmind/ab-testing";

const pct = parseEnvPct(process.env.REACT_APP_HPDP_PCT);
const shouldOverlayHPDP = getBucketedValue("ab-tests", "am_hpdp", pct);

if (shouldOverlayHPDP) {
  overlayHpdp(); // overlay the host PDP with the HPDP experience
}

On the first visit, getBucketedValue randomly assigns the user to the experiment group and saves the result to localStorage. On every subsequent visit, the stored assignment is returned — no re-randomization.

API Reference

getBucketedValue(storageKey, experimentName, pctTrue): boolean

The main function. Assigns a user to an experiment bucket and persists the result.

Parameter Type Description
storageKey string localStorage key for storing all experiment assignments (e.g., "ab-tests")
experimentName string Unique identifier for the experiment (e.g., "am_hpdp")
pctTrue number Probability (0–100) of assigning true on first draw

Returns true if the user is in the experiment group, false for control.


parseEnvPct(envVar): number

Parses an environment variable string as a percentage. Useful for configuring rollout percentages without hardcoding.

import { parseEnvPct } from "@adeptmind/ab-testing";

const pct = parseEnvPct(process.env.REACT_APP_EXPERIMENT_PCT);
const showFeature = getBucketedValue("ab-tests", "new-feature", pct);

Throws if the value is undefined, non-numeric, or outside 0–100.


drawFromUniform(pctTrue): boolean

A single non-sticky random draw. Returns true with probability pctTrue / 100.

import { drawFromUniform } from "@adeptmind/ab-testing";

if (drawFromUniform(30)) {
  // ~30% chance per call — not persisted
}

getLocalJson<T>(key): T | null

Reads and parses a JSON value from localStorage. Returns null if the key is missing or the data is malformed.

setLocalJson<T>(key, value): void

Serializes a value as JSON and writes it to localStorage.

Two-Tier Experimentation

This is how experimentation works when integrating with the AdeptMind HPDP experience.

This library is designed for a two-tier experimentation pattern:

Tier 1 — AMT script buckets am_hpdp at page load. If true, HPDP overlay is activated. The assignment is written to window.adeptmind_ab_testing so Tier 2 can read it.

Tier 2 — Host PDP's Alloy script (Adobe Target) runs post-hydration and reads window.adeptmind_ab_testing["am_hpdp"]:

  • true — use default splits for other Adobe Target experiments (HPDP overlays the page, so these splits don't affect the HPDP experience)
  • false — proceed with regular experiment splits on the native PDP (can proceed as if HPDP experiment does not exist)
// Tier 1 — AMT script at page load
import { getBucketedValue, parseEnvPct } from "@adeptmind/ab-testing";

const pct = parseEnvPct(process.env.REACT_APP_HPDP_PCT);
const shouldOverlayHPDP = getBucketedValue("ab-tests", "am_hpdp", pct);
// window.adeptmind_ab_testing === { "am_hpdp": true }

if (shouldOverlayHPDP) {
  overlayHpdp();
}

// Tier 2 — Adobe Target audience rule (post-hydration):
if (window.adeptmind_ab_testing["am_hpdp"] === true){
//   → lock target splits to a consistent default set. This ensures 
//     that HPDP does not affect other ongoing experiment data
} else { 
//   → regular experiment splitting logic as if HPDP does not exist
}

How Bucketing Works

First visit:
  1. Read localStorage["ab-tests"]          → null (empty)
  2. Draw random number, compare to pctTrue → true
  3. Write localStorage["ab-tests"]         → { "am_hpdp": true }
  4. Set window.adeptmind_ab_testing["am_hpdp"] = true
  5. Return true                            → Tier 1 activates HPDP overlay
  6. Tier 2 (Adobe Target) reads window.adeptmind_ab_testing["am_hpdp"]
     → true → lock target splits to default set

Second visit:
  1. Read localStorage["ab-tests"]         → { "am_hpdp": true }
  2. Key "am_hpdp" exists                  → skip random draw
  3. Set window.adeptmind_ab_testing["am_hpdp"] = true
  4. Return true → same experience as first visit

All experiments under the same storageKey are stored in a single JSON object. Adding a new experiment never overwrites existing assignments.

FAQs

How do I run an experiment at 50/50?

Pass 50 as the pctTrue parameter:

const inExperiment = getBucketedValue("ab-tests", "my-experiment", 50);

How do users stay in the same bucket?

The first call writes the assignment to localStorage. Every subsequent call reads from localStorage and returns the stored value — the random draw only happens once.

Can I run multiple experiments at once?

Yes. Use the same storageKey with different experimentName values. Each experiment is tracked independently:

const showNewNav = getBucketedValue("ab-tests", "new-nav", 50);
const showNewFooter = getBucketedValue("ab-tests", "new-footer", 30);

How do I reset/clear an experiment?

Remove the assignment from localStorage:

localStorage.removeItem("ab-tests");

This clears all experiments under that key. To clear a single experiment, read the object, delete the key, and write it back:

const stored = JSON.parse(localStorage.getItem("ab-tests") ?? "{}");
delete stored["my-experiment"];
localStorage.setItem("ab-tests", JSON.stringify(stored));

How does this work with Adobe Target?

Adobe Target (Tier 2) runs after page hydration via the host PDP's Alloy script. By that point, getBucketedValue has already written bucket assignments to window.adeptmind_ab_testing. Adobe Target reads window.adeptmind_ab_testing["am_hpdp"] in its audience rules: if true, it applies default splits for its own experiments (since HPDP overlays the page anyway); if false, it proceeds with regular experiment splits on the native PDP.

Does this work with SSR?

No — this library depends on localStorage, which is only available in the browser. For server-side rendering, bucket on the server using a different mechanism (e.g., cookie-based or user-ID-based hashing) and pass the assignment to the client.

How do I configure the percentage from an environment variable?

Use parseEnvPct to safely parse the env var:

const pct = parseEnvPct(process.env.REACT_APP_FEATURE_PCT);
const showFeature = getBucketedValue("ab-tests", "my-feature", pct);

This throws at startup if the env var is missing or invalid, so you catch configuration errors early.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors