Własne testy A/B w GTM i Firebase — bez dedykowanego narzędzia

Dedykowane platformy do testów A/B potrafią kosztować więcej niż cała reszta stacku analitycznego. A jeśli masz porządny tracking, solidny test zrobisz samodzielnie — w Google Tag Managerze na webie i w Firebase na mobile — za darmo i pod pełną kontrolą. Jeden warunek: test jest tylko tak wiarygodny, jak dane pod nim (o tym pisałem w architekturze danych pod AI — tu obowiązuje to samo).

Uwaga: część feature’ów, które realnie testowałem, była na tyle specyficzna dla jednego biznesu, że celowo je tu uogólniam. Liczy się metoda, nie konkretny przycisk.

Sedno: first-party cookie trzyma wariant, GTM nim zarządza, GA4 go raportuje. Tag custom-HTML przy wejściu losuje wariant, zapisuje go w ciasteczku i wysyła do dataLayer:

// GTM custom HTML — przydział wariantu
// i zapis do first-party cookie
(function () {
  function getCookie(name) {
    var m = document.cookie.match(
      new RegExp('(?:^|; )' + name + '=([^;]*)'));
    return m ? m[1] : null;
  }
  function setCookie(name, value, days) {
    var exp = new Date(Date.now() + days * 864e5);
    document.cookie = name + '=' + value +
      '; expires=' + exp.toUTCString() +
      '; path=/; SameSite=Lax';
  }
  var test = 'ab_cta'; // nazwa testu
  // user ma już wariant — nie losuj ponownie
  if (getCookie(test)) return;
  var variant = Math.random() < 0.5 ? 'v0' : 'v1';
  setCookie(test, variant, 30); // ciasteczko na 30 dni
  dataLayer.push({
    event: 'ab_assign',
    ab_test: test,
    ab_variant: variant
  });
})();

Trzy rzeczy, które robią z tego system, a nie hak:

  • Jeden wspólny tag odpala się na evencie ab_assign i obsługuje wszystkie testy — każdy test pcha ten sam event z własną nazwą i wariantem. Nie mnożysz tagów.
  • Wariant jako parametr zdarzenia i wymiar niestandardowy. Tag GA4 na ab_assign wysyła ab_test i ab_variant jako parametry zdarzenia — z nich liczy zapytanie w BigQuery niżej. Żeby segmentować raporty w samym GA4, ustaw ab_variant także jako właściwość użytkownika (zakresu sesji GA4 nie ma).
  • Przydzielaj w punkcie ekspozycji. Losuj tam, gdzie user staje się uprawniony do testu — dla zmiany widocznej tylko w koszyku rób to na wejściu do koszyka. Przydzielanie na każdej stronie nie psuje proporcji 50/50 wśród wchodzących do koszyka (losowanie jest niezależne od tego, czy ktoś tam dotrze), ale rozmywa wynik: wrzucasz do analizy tłum userów, którzy zmiany nigdy nie zobaczyli, i tracisz moc statystyczną. Wcześniejszy przydział ma sens tylko wtedy, gdy wariant musi być gotowy przed daną stroną (zmiana widoczna już wcześniej albo ryzyko „mignięcia” przy decyzji na renderze) — i wtedy i tak filtruj analizę do faktycznie eksponowanych. To ta sama zasada, co activation event w Firebase.

Gdy wyłoni się zwycięzca, zmieniasz skrypt tak, by wymusić wygrany wariant u wszystkich (warunek nadpisujący ciasteczko dla pozostałych grup) i puszczasz 100% ruchu.

A/B testy w Firebase Remote Config (mobile)

Na mobile nie ma ciasteczek — jest Firebase A/B Testing na Remote Config. Konsola Firebase → A/B Testing → Create experiment → Remote config. Kilka rzeczy, które nie są oczywiste:

  • Osobny eksperyment per platforma (Android i iOS). Nazwy prefiksuj [AND] / [IOS].
  • Targeting: wybierasz aplikację i ustawiasz suwak na 100% userów (lub warunki).
  • Activation event — tu jest pułapka. To zdarzenie decyduje, kto wchodzi do testu (np. view_item dla oglądających kartę produktu). Ale activation event musi być eventem bez parametrów. Jeśli chcesz aktywować na parze event + parametr (np. tylko karty z ofertą 3P), musisz stworzyć dedykowany event (np. view_item_3p). Ta sama zasada dotyczy metryk w Goals.
  • Goals: primary metric + zawsze dorzuć purchase i przychód jako KPI kontrolne.
  • Variants mapują się na wartości klucza Remote Config. Jeśli klucza nie ma na liście — developerzy muszą najpierw wrzucić go na produkcję.

Klient czyta wartość RC i przełącza UI:

let mode = RemoteConfig.remoteConfig()
  .configValue(forKey: "product_description_mode")
  .stringValue
switch mode {
case "minimal":  showMinimalDescription()
case "detailed": showDetailedDescription()
default:         showClassicDescription() // kontrolny
}

Firebase liczy istotność częstościowo — pokazuje p-value i przedział ufności, tak jak klasyczny test hipotez. Co p-value mówi, a czego nie, i czym różni się od podejścia bayesowskiego, rozkładam w tekście o testach A/B dla analityków.

Analiza wyników: konwersja i przychód per wariant (GA4 → BigQuery)

Konsola GA4 wystarczy do podglądu, ale realną kontrolę daje SQL na eksporcie GA4 do BigQuery — zwłaszcza gdy chcesz policzyć konwersję i przychód dokładnie tak, jak definiujesz je w biznesie:

-- Wyniki testu A/B per wariant:
-- użytkownicy, kupujący, przychód i ARPU

-- 1. jeden wiersz na użytkownika: pierwsze
--    przypisanie w TYM teście i liczba wariantów
WITH assignment AS (
  SELECT
    user_pseudo_id,
    MIN(event_timestamp) AS first_ts,
    ANY_VALUE(variant) AS variant,
    COUNT(DISTINCT variant) AS variants_seen
  FROM (
    SELECT
      user_pseudo_id,
      event_timestamp,
      (SELECT value.string_value
         FROM UNNEST(event_params)
         WHERE key = 'ab_variant') AS variant,
      (SELECT value.string_value
         FROM UNNEST(event_params)
         WHERE key = 'ab_test') AS test
    FROM `projekt.analytics_XXXXXX.events_*`
    WHERE event_name = 'ab_assign'
      AND _TABLE_SUFFIX
        BETWEEN '20260501' AND '20260531'
  )
  -- wspólny event dla wszystkich testów
  WHERE test = 'ab_cta' AND variant IS NOT NULL
  GROUP BY user_pseudo_id
),
-- 2. zakupy w okresie testu
purchase_events AS (
  SELECT
    user_pseudo_id,
    event_timestamp,
    ecommerce.purchase_revenue AS revenue
  FROM `projekt.analytics_XXXXXX.events_*`
  WHERE event_name = 'purchase'
    AND _TABLE_SUFFIX
      BETWEEN '20260501' AND '20260531'
),
-- 3. tylko zakupy PO pierwszym przypisaniu,
--    zsumowane na użytkownika
purchases AS (
  SELECT
    a.user_pseudo_id,
    COUNT(*) AS transactions,
    SUM(p.revenue) AS revenue
  FROM assignment AS a
  JOIN purchase_events AS p
    ON p.user_pseudo_id = a.user_pseudo_id
    AND p.event_timestamp > a.first_ts
  GROUP BY a.user_pseudo_id
)
SELECT
  a.variant,
  COUNT(*) AS users,
  COUNT(p.user_pseudo_id) AS buyers,
  SAFE_DIVIDE(
    COUNT(p.user_pseudo_id), COUNT(*)
  ) AS conversion_rate,
  IFNULL(SUM(p.revenue), 0) AS revenue,
  SAFE_DIVIDE(
    IFNULL(SUM(p.revenue), 0), COUNT(*)
  ) AS arpu
FROM assignment AS a
LEFT JOIN purchases AS p
  USING (user_pseudo_id)
-- bez użytkowników z dwoma wariantami
WHERE a.variants_seen = 1
GROUP BY a.variant
ORDER BY a.variant;

Masz w jednej tabeli wielkość grup, współczynnik konwersji, przychód i ARPU — czyli wszystko, czego trzeba do decyzji, którą wersję wypuścić na 100%. Liczby z tej tabeli wpisz do kalkulatora istotności testu A/B — powie, czy różnica jest istotna i jak duża może naprawdę być.

Trzy miejsca, w których to zapytanie najłatwiej popsuć:

  • Filtr na nazwę testu. Wspólny event ab_assign obsługuje wszystkie testy, więc bez test = 'ab_cta' mieszasz wyniki kilku testów naraz.
  • Zakup po przypisaniu. Zakup sprzed pierwszego ab_assign nie mógł zależeć od wariantu, a liczony rozmywa różnicę między grupami.
  • Jeden wiersz na użytkownika przed złączeniem. Ktoś z wygasłym cookie dostaje drugie ab_assign — czasem z innym wariantem. Bez agregacji jego przychód liczy się podwójnie, a bez filtra variants_seen = 1 trafia do obu grup.

Pułapki, o których łatwo zapomnieć

  • Zgody. Ciasteczko testu nie jest niezbędne do działania strony, więc wymaga zgody (Prawo komunikacji elektronicznej, za dyrektywą ePrivacy) — losuj i zapisuj dopiero po niej. Consent Mode tylko przekazuje tę decyzję tagom Google.
  • Safari skraca ciasteczko. Ciasteczko ustawione przez JavaScript żyje w Safari najwyżej 7 dni (ITP), niezależnie od expires. Kto wróci po tygodniu, zostanie wylosowany ponownie — stąd w SQL filtr variants_seen = 1 i stąd długie testy tracą wiarygodność.
  • Jeden user = jeden wariant. Ciasteczko to pamięć przydziału; nie losuj ponownie przy każdej odsłonie.
  • Firebase: bez parametrów. Activation event i metryki celu muszą być eventami bez parametrów — inaczej test nie ruszy.
  • Spójność web↔mobile. To dwa osobne mechanizmy (cookie vs Remote Config) — pilnuj, by definicja wariantu i metryki były takie same po obu stronach.

Cała reszta to dyscyplina: dobra warstwa zdarzeń pod spodem, jeden wariant na użytkownika i metryka liczona tak samo dla każdej grupy. Narzędzie jest tanie — wiarygodność bierze się z trackingu.

Powiązane wpisy