w-funnel/docs/GA_AB_TEST_EXAMPLES.md
2025-10-23 21:16:09 +02:00

573 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📊 Примеры работы AB-тестов и Google Analytics
## 🎬 Сценарий 1: Первый вход пользователя
### Воронка с 2 AB-тестами
```json
{
"meta": {
"id": "soulmate",
"googleAnalyticsId": "G-XXXXXXXXXX"
},
"screens": [
{
"id": "payment",
"variants": [{
"conditions": [{
"conditionType": "unleash",
"unleashFlag": "trial-button-test",
"unleashVariants": ["v1"]
}]
}]
},
{
"id": "gender",
"navigation": {
"rules": [{
"conditions": [{
"conditionType": "unleash",
"unleashFlag": "onboarding-flow",
"unleashVariants": ["short"]
}]
}]
}
}
]
}
```
### Временная последовательность
```
┌─────────────────────────────────────────────────────────┐
│ T+0ms: Пользователь открывает /soulmate/payment │
└─────────────────────────────────────────────────────────┘
├─ Server-side render
├─ layout.tsx загружается
┌─────────────────────────────────────────────────────────┐
│ T+50ms: UnleashProvider инициализируется │
│ Подключение к Unleash API │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+100ms: PixelsProvider монтируется │
│ GoogleAnalytics загружает gtag.js │
│ window.gtag становится доступен │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+200ms: FunnelUnleashWrapper сканирует воронку │
│ Находит флаги: │
│ • trial-button-test (из payment.variants) │
│ • onboarding-flow (из gender.navigation) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+300ms: FlagVariantFetcher загружает варианты │
│ Unleash SDK возвращает: │
│ • trial-button-test → "v1" │
│ • onboarding-flow → "short" │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+350ms: activeVariants обновляется │
│ { │
│ "trial-button-test": "v1", │
│ "onboarding-flow": "short" │
│ } │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+400ms: FunnelRuntime монтируется │
│ currentScreen = payment экран │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+410ms: currentScreenFlags вычисляется │
│ Анализирует payment экран: │
│ • payment.variants → trial-button-test │
│ • payment.navigation → нет флагов │
│ Результат: ["trial-button-test"] │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+420ms: useEffect срабатывает │
│ currentScreenFlags = ["trial-button-test"] │
│ activeVariants["trial-button-test"] = "v1" │
│ │
│ sendUnleashImpression("trial-button-test", "v1")│
└─────────────────────────────────────────────────────────┘
├─ Проверка: вариант валидный ✅
├─ Проверка: браузер ✅
├─ Проверка sessionStorage: пусто ✅
├─ Проверка window.gtag: есть ✅
┌─────────────────────────────────────────────────────────┐
│ T+421ms: ОТПРАВКА В GOOGLE ANALYTICS │
│ │
│ window.gtag("event", "experiment_impression", { │
│ app_name: "witlab-funnel", │
│ feature: "trial-button-test", │
│ treatment: "v1" │
│ }); │
│ │
│ sessionStorage.setItem( │
│ "unleash_impression_trial-button-test_v1", │
│ "true" │
│ ); │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+422ms: Network request отправлен │
│ POST /g/collect?v=2&tid=G-XXX │
│ &en=experiment_impression │
│ &ep.feature=trial-button-test │
│ &ep.treatment=v1 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Console (development): │
│ [Unleash Impression] ✅ Sent successfully: │
│ { feature: "trial-button-test", variant: "v1" } │
└─────────────────────────────────────────────────────────┘
```
---
## 🎬 Сценарий 2: Переход на следующий экран
```
┌─────────────────────────────────────────────────────────┐
│ T+0ms: Пользователь нажимает "Continue" │
│ Router.push("/soulmate/gender") │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+50ms: URL изменился → /soulmate/gender │
│ FunnelRuntime ре-рендерится │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+100ms: currentScreen обновляется │
│ currentScreen = gender экран │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+110ms: currentScreenFlags пересчитывается │
│ Анализирует gender экран: │
│ • gender.variants → нет флагов │
│ • gender.navigation → onboarding-flow │
│ Результат: ["onboarding-flow"] │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+120ms: useEffect срабатывает (deps изменились) │
│ currentScreenFlags = ["onboarding-flow"] │
│ activeVariants["onboarding-flow"] = "short" │
│ │
│ sendUnleashImpression("onboarding-flow", "short")│
└─────────────────────────────────────────────────────────┘
├─ Проверка sessionStorage:
│ "unleash_impression_onboarding-flow_short"
│ не найдено ✅
┌─────────────────────────────────────────────────────────┐
│ T+121ms: ОТПРАВКА ВТОРОГО СОБЫТИЯ │
│ │
│ window.gtag("event", "experiment_impression", { │
│ app_name: "witlab-funnel", │
│ feature: "onboarding-flow", │
│ treatment: "short" │
│ }); │
│ │
│ sessionStorage.setItem( │
│ "unleash_impression_onboarding-flow_short", │
│ "true" │
│ ); │
└─────────────────────────────────────────────────────────┘
```
### Состояние sessionStorage
```javascript
// После первого экрана:
{
"unleash_impression_trial-button-test_v1": "true"
}
// После второго экрана:
{
"unleash_impression_trial-button-test_v1": "true",
"unleash_impression_onboarding-flow_short": "true"
}
```
---
## 🎬 Сценарий 3: Возврат назад (защита от дубликатов)
```
┌─────────────────────────────────────────────────────────┐
│ T+0ms: Пользователь нажимает "Back" │
│ Router.push("/soulmate/payment") │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+50ms: URL изменился → /soulmate/payment │
│ FunnelRuntime ре-рендерится │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+100ms: currentScreen = payment (снова) │
│ currentScreenFlags = ["trial-button-test"] │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+110ms: useEffect срабатывает │
│ sendUnleashImpression("trial-button-test", "v1")│
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Проверка sessionStorage: │
│ │
│ const key = "unleash_impression_trial-button-test_v1"; │
│ const alreadySent = sessionStorage.getItem(key); │
│ // "true" ← Уже отправлялось! │
│ │
│ if (alreadySent) { │
│ return; // ❌ ОТПРАВКА НЕ ПРОИСХОДИТ │
│ } │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Console (development): │
│ [Unleash Impression] Skipped (already sent): │
│ { feature: "trial-button-test", variant: "v1" } │
└─────────────────────────────────────────────────────────┘
```
---
## 🎬 Сценарий 4: Перезагрузка страницы (F5)
```
┌─────────────────────────────────────────────────────────┐
│ Пользователь на экране /soulmate/gender │
│ sessionStorage содержит: │
│ { │
│ "unleash_impression_trial-button-test_v1": "true", │
│ "unleash_impression_onboarding-flow_short": "true" │
│ } │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+0ms: Пользователь нажимает F5 │
│ Браузер перезагружает страницу │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ ВАЖНО: sessionStorage НЕ очищается при перезагрузке! │
│ Данные сохраняются! │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ T+500ms: Страница загружена заново │
│ FunnelRuntime монтируется снова │
│ currentScreen = gender │
│ currentScreenFlags = ["onboarding-flow"] │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ useEffect срабатывает: │
│ sendUnleashImpression("onboarding-flow", "short") │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Проверка sessionStorage: │
│ "unleash_impression_onboarding-flow_short" = "true" │
│ │
│ ❌ УЖЕ ОТПРАВЛЯЛОСЬ - ПРОПУСКАЕМ │
└─────────────────────────────────────────────────────────┘
```
**Результат:** Даже после F5 события НЕ отправляются повторно.
---
## 🎬 Сценарий 5: Новая вкладка / Закрытие браузера
```
┌─────────────────────────────────────────────────────────┐
│ ВКЛАДКА 1: Пользователь прошел воронку │
│ sessionStorage: │
│ { "unleash_impression_trial-button-test_v1": "true" } │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Пользователь открывает НОВУЮ ВКЛАДКУ │
с тем же URL: /soulmate/payment │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ ВКЛАДКА 2: Новая сессия браузера! │
│ sessionStorage пустой: │
│ {} │
│ │
│ (sessionStorage изолирован для каждой вкладки) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ FunnelRuntime загружается: │
│ sendUnleashImpression("trial-button-test", "v1") │
│ │
│ Проверка sessionStorage: ПУСТО │
│ ✅ СОБЫТИЕ ОТПРАВЛЯЕТСЯ ЗАНОВО
└─────────────────────────────────────────────────────────┘
```
**Важно:** sessionStorage изолирован для каждой вкладки браузера.
---
## 📊 Что видно в Google Analytics
### Events Report
```
Event name: experiment_impression
Total events: 2,345
By feature parameter:
┌────────────────────┬───────────┬────────┐
│ Feature │ Count │ Users │
├────────────────────┼───────────┼────────┤
│ trial-button-test │ 1,234 │ 987 │
│ onboarding-flow │ 1,111 │ 896 │
└────────────────────┴───────────┴────────┘
By treatment parameter:
┌────────────────────┬───────────┬────────┐
│ Treatment │ Count │ Users │
├────────────────────┼───────────┼────────┤
│ v1 │ 634 │ 507 │
│ v2 │ 600 │ 480 │
│ control │ 555 │ 445 │
│ short │ 556 │ 448 │
└────────────────────┴───────────┴────────┘
Cross-tabulation:
┌────────────────────┬───────────┬────────┐
│ Feature + Treatment│ Count │ Users │
├────────────────────┼───────────┼────────┤
│ trial-button-test │ │ │
│ ├─ v1 │ 634 │ 507 │
│ └─ v2 │ 600 │ 480 │
│ │ │ │
│ onboarding-flow │ │ │
│ ├─ short │ 556 │ 448 │
│ └─ control │ 555 │ 445 │
└────────────────────┴───────────┴────────┘
```
### DebugView (Realtime)
```
User: 12345abc
Session ID: sess_xyz789
Events:
┌─────────┬────────────────────────┬────────────────────┐
│ Time │ Event │ Parameters │
├─────────┼────────────────────────┼────────────────────┤
│ 14:32:10│ page_view │ page: /payment │
│ 14:32:11│ experiment_impression │ feature: trial-.. │
│ │ │ treatment: v1 │
│ 14:32:45│ page_view │ page: /gender │
│ 14:32:46│ experiment_impression │ feature: onboard.. │
│ │ │ treatment: short │
└─────────┴────────────────────────┴────────────────────┘
```
---
## 🔍 Network Tab Примеры
### Первое impression событие
```http
POST /g/collect HTTP/1.1
Host: www.google-analytics.com
Query Parameters:
v=2
tid=G-XXXXXXXXXX
_p=1234567890
cid=abc-def-ghi-jkl
en=experiment_impression ← Event Name
epn.value=1
ep.app_name=witlab-funnel ← Custom Parameter
ep.feature=trial-button-test ← Custom Parameter
ep.treatment=v1 ← Custom Parameter
_s=1
```
### Второе impression событие
```http
POST /g/collect HTTP/1.1
Host: www.google-analytics.com
Query Parameters:
v=2
tid=G-XXXXXXXXXX
en=experiment_impression
ep.app_name=witlab-funnel
ep.feature=onboarding-flow ← Другой флаг
ep.treatment=short ← Другой вариант
_s=2
```
---
## 🧪 Тестирование
### 1. Очистка истории для повторного тестирования
```javascript
// В консоли браузера:
import { clearUnleashImpressions } from "@/lib/funnel/unleash";
// Очистит все impression ключи
clearUnleashImpressions();
// Или вручную:
Object.keys(sessionStorage)
.filter(key => key.startsWith("unleash_impression_"))
.forEach(key => sessionStorage.removeItem(key));
// После этого события отправятся заново
```
### 2. Проверка текущего состояния
```javascript
// Посмотреть все impression ключи:
Object.keys(sessionStorage)
.filter(key => key.startsWith("unleash_impression_"))
.forEach(key => {
console.log(key, sessionStorage.getItem(key));
});
// Вывод:
// unleash_impression_trial-button-test_v1 "true"
// unleash_impression_onboarding-flow_short "true"
```
### 3. Имитация нового пользователя
```javascript
// 1. Очистить sessionStorage
sessionStorage.clear();
// 2. Открыть воронку в новой вкладке Incognito
// ИЛИ
// 3. Перезапустить браузер
// События отправятся как для нового пользователя
```
---
## 📈 Практические примеры AB тестов
### Тест 1: Кнопка оплаты
```json
{
"id": "payment",
"variants": [{
"conditions": [{
"conditionType": "unleash",
"unleashFlag": "trial-payment-button",
"unleashVariants": ["v1"]
}],
"override": {
"bottomActionButton": {
"text": "Start 7-Day Free Trial"
}
}
}]
}
```
**Unleash возвращает:**
- 50% пользователей: `v1` (кнопка "Start 7-Day Free Trial")
- 50% пользователей: `disabled` (кнопка "Continue to Payment")
**GA события:**
```javascript
// Группа A (v1):
{ feature: "trial-payment-button", treatment: "v1" }
// Группа B (disabled): НЕТ события
// потому что sendUnleashImpression пропускает variant="disabled"
```
### Тест 2: Короткая vs длинная воронка
```json
{
"id": "intro",
"navigation": {
"rules": [{
"conditions": [{
"conditionType": "unleash",
"unleashFlag": "funnel-length-test",
"unleashVariants": ["short"]
}],
"nextScreenId": "payment"
}],
"default": "details"
}
}
```
**Unleash возвращает:**
- 50%: `short` → переход сразу на payment (3 экрана)
- 50%: `disabled` → переход на details (5 экранов)
**GA события для обеих групп:**
```javascript
// Все пользователи видят intro экран:
{ feature: "funnel-length-test", treatment: "short" }
{ feature: "funnel-length-test", treatment: "disabled" }
// Но дальше идут разными путями!
```
---
## ✅ Контрольный список
### Перед запуском AB теста:
- [ ] Google Analytics ID настроен в `funnel.meta.googleAnalyticsId`
- [ ] Unleash флаг создан и активен
- [ ] Unleash возвращает правильные варианты
- [ ] События отправляются (проверить Network tab)
- [ ] События видны в GA DebugView
- [ ] sessionStorage работает корректно
### При отладке:
- [ ] `window.gtag` определен (в консоли)
- [ ] Console логи показывают "Sent successfully"
- [ ] Network tab показывает POST /g/collect
- [ ] sessionStorage содержит impression ключи
- [ ] DebugView показывает события в реальном времени