Training

The Experimentation Lab

Twelve lessons that walk you from your first selector to reading your first result. Every lesson points back to a real surface on this shop you can practise on.

Lesson 01Beginner

What is CRO Development?

A practical introduction to the role of a CRO developer — what we build, why it differs from product engineering, and how experiments earn their keep.

6 minRead →
Lesson 02Beginner

Anatomy of an A/B Test

From hypothesis to readout: every test has the same skeleton. Learn it once and you can run them anywhere.

8 minRead →
Lesson 03Beginner

Installing Webtrends Optimize

How the WTO snippet works, where it goes, and why placement matters for performance and flicker.

9 minRead →
Lesson 04Intermediate

Selecting Elements Like a Witch

Stable selectors are the difference between a test that ships and a test that breaks on Tuesday.

10 minRead →
Lesson 05Advanced

Watching the DOM with MutationObserver

For single-page apps and late-hydrating components: stop polling, start observing.

12 minRead →
Lesson 06Intermediate

Waiting for an Element to Exist

A small helper that solves 80% of timing bugs in client-side experiments.

7 minRead →
Lesson 07Intermediate

Cast Your First Test on This Shop

Walk through a real experiment on the Grimoire Shop hero CTA — from selector to variant code to readout.

15 minRead →
Lesson 08Intermediate

DOM Manipulation Without Layout Shock

How to change the page in a variant without trashing Core Web Vitals.

10 minRead →
Lesson 09Intermediate

Reading Your Results

Significance, confidence intervals, and the discipline of not peeking.

10 minRead →
Lesson 10Intermediate

Segmentation Without Self-Deception

Slicing your audience reveals truth — or fabricates it, if you slice too much.

9 minRead →
Lesson 11Beginner

Metrics That Matter

Choosing a primary metric that maps to business value — and a guardrail that prevents you fooling yourself.

7 minRead →
Lesson 12Intermediate

From Test to Insight

Every test should produce a portable lesson, even when the variant loses.

8 minRead →
Snippets

Code Snippets Library

Copy-and-paste primitives every CRO developer should keep within reach.

waitForElement

Resolve a promise once a selector exists, with a timeout safety net.

javascript
function waitForElement(selector, { root = document.body, timeout = 8000 } = {}) {
  return new Promise((resolve, reject) => {
    const found = root.querySelector(selector);
    if (found) return resolve(found);
    const obs = new MutationObserver(() => {
      const el = root.querySelector(selector);
      if (el) { obs.disconnect(); resolve(el); }
    });
    obs.observe(root, { childList: true, subtree: true });
    setTimeout(() => { obs.disconnect(); reject(new Error('Timeout: ' + selector)); }, timeout);
  });
}

onRouteChange (MutationObserver)

Re-run your variant code when an SPA changes route without a full page load.

javascript
function onRouteChange(callback) {
  let last = location.pathname + location.search;
  const fire = () => {
    const now = location.pathname + location.search;
    if (now !== last) { last = now; callback(now); }
  };
  new MutationObserver(fire).observe(document.body, { childList: true, subtree: true });
  window.addEventListener('popstate', fire);
}

WTO goal tracking

Fire a Webtrends Optimize goal when the conversion event happens.

javascript
function trackWtoGoal(goalId) {
  if (window.wt_sdc_namespace && window.wt_sdc_namespace.dcsMultiTrack) {
    window.wt_sdc_namespace.dcsMultiTrack(
      'DCS.dcsuri', '/goal/' + goalId,
      'WT.ti', 'Experiment Goal: ' + goalId
    );
  }
}

GA4 event tracking

Send a custom event to GA4 with experiment + variant labels.

javascript
function trackGa4(eventName, params = {}) {
  if (typeof window.gtag !== 'function') return;
  window.gtag('event', eventName, {
    experiment_id: params.experimentId,
    variant_id: params.variantId,
    ...params,
  });
}

Variant assignment from cookie

Persist a 50/50 split per visitor using a first-party cookie.

javascript
function assignVariant(experimentId) {
  const key = 'exp_' + experimentId;
  const existing = document.cookie.split('; ').find((c) => c.startsWith(key + '='));
  if (existing) return existing.split('=')[1];
  const variant = Math.random() < 0.5 ? 'control' : 'v1';
  const oneYear = 60 * 60 * 24 * 365;
  document.cookie = key + '=' + variant + '; path=/; max-age=' + oneYear + '; SameSite=Lax; Secure';
  return variant;
}

Anti-flicker shield

Hide the testable region for up to 4s, lift on variant apply or timeout.

javascript
(function antiFlicker(){
  const style = document.createElement('style');
  style.id = 'wto-anti-flicker';
  style.textContent = '.wto-hide { opacity: 0 !important; transition: opacity .2s; }';
  document.head.appendChild(style);
  document.documentElement.classList.add('wto-hide');
  const lift = () => document.documentElement.classList.remove('wto-hide');
  window.__liftAntiFlicker = lift;
  setTimeout(lift, 4000);
})();