Development of an A/B testing module for 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Developing an A/B Testing Module for 1C-Bitrix

An A/B test is not just "show half of users a red button". It's correct audience segmentation, stable variant assignment (one user always sees one variant), tracking of goal events, statistical significance of results. Without a module, developers usually do this through if (rand(0,1)) — and get flickering variants, useless statistics, and inability to scale experiments. A proper module solves all these problems.

Data Model

Module vendor.abtest:

  • b_vendor_ab_experiment — experiments: id, name, code, status (draft/running/paused/finished), traffic_percent (10-100), started_at, finished_at, goal_event, description
  • b_vendor_ab_variant — variants: id, experiment_id, name, weight (for uneven split), is_control
  • b_vendor_ab_assignment — assignments: id, experiment_id, variant_id, user_id (or null), session_id, created_at
  • b_vendor_ab_event — goal events: id, experiment_id, variant_id, assignment_id, event_type, created_at
  • b_vendor_ab_stat — aggregated statistics (daily snapshots): experiment_id, variant_id, date, participants, conversions, revenue

Stable Variant Assignment

A user must always see the same variant. Assignment is performed once and saved:

class AssignmentService
{
    public function getVariant(string $experimentCode): ?Variant
    {
        $experiment = ExperimentTable::getByCode($experimentCode);
        if (!$experiment || $experiment['STATUS'] !== 'running') return null;

        $sessionId = $this->getSessionId();
        $userId    = $GLOBALS['USER']->GetID() ?: null;

        // Look for existing assignment
        $existing = AssignmentTable::getList([
            'filter' => [
                'EXPERIMENT_ID' => $experiment['ID'],
                'LOGIC'         => 'OR',
                'USER_ID'       => $userId,
                'SESSION_ID'    => $sessionId,
            ],
        ])->fetch();

        if ($existing) {
            return VariantTable::getById($existing['VARIANT_ID'])->fetch();
        }

        // Check traffic percentage (deterministically by session_id)
        $hash = crc32($sessionId . $experimentCode) % 100;
        if ($hash >= $experiment['TRAFFIC_PERCENT']) return null; // user outside test

        // Select variant by weights
        $variant = $this->selectVariant($experiment['ID']);

        AssignmentTable::add([
            'EXPERIMENT_ID' => $experiment['ID'],
            'VARIANT_ID'    => $variant['ID'],
            'USER_ID'       => $userId,
            'SESSION_ID'    => $sessionId,
        ]);

        return $variant;
    }
}

Deterministic hash by session_id + experiment_code guarantees stable traffic percentage membership without storing extra data.

Usage in Templates

In component template or layout:

$abTest  = \Vendor\AbTest\AbTestService::getInstance();
$variant = $abTest->getVariant('checkout_button_color');

$buttonClass = match ($variant?->getName()) {
    'green'  => 'btn-success',
    'orange' => 'btn-warning',
    default  => 'btn-primary', // control variant or outside test
};

In JavaScript:

// Variant data passed through data-attribute or JS-variable
const variant = window.ABTEST_VARIANTS?.checkout_button_color;
if (variant === 'new_form') {
    document.querySelector('.checkout-form').classList.add('new-form');
}

Tracking Goal Events

We track conversion — purchase, registration, CTA click:

// When order is paid
AddEventHandler('sale', 'OnSaleOrderPaid', function(\Bitrix\Main\Event $event) {
    $orderId = $event->getParameter('id');
    $order   = \Bitrix\Sale\Order::load($orderId);

    \Vendor\AbTest\EventTracker::track('purchase', [
        'user_id'  => $order->getUserId(),
        'revenue'  => $order->getPrice(),
        'order_id' => $orderId,
    ]);
});

EventTracker::track() finds active assignments for a given user and records event in b_vendor_ab_event.

Statistical Significance

In the administrative interface for each experiment, the following is calculated:

  • Conversion by variants (conversion rate = events / participants)
  • p-value using chi-squared test (χ²) — significance threshold 95%
  • Uplift — relative improvement versus control variant
  • Sample ratio mismatch — check if variant ratio was not violated
// χ² test for two variants
function chiSquaredTest(int $a_visitors, int $a_conversions, int $b_visitors, int $b_conversions): float
{
    $a_rate = $a_conversions / $a_visitors;
    $b_rate = $b_conversions / $b_visitors;
    $pooled = ($a_conversions + $b_conversions) / ($a_visitors + $b_visitors);

    $expected_a = $pooled * $a_visitors;
    $expected_b = $pooled * $b_visitors;

    return (($a_conversions - $expected_a) ** 2 / $expected_a) +
           (($b_conversions - $expected_b) ** 2 / $expected_b);
}

p-value < 0.05 → result is statistically significant.

Development Timeline

Stage Duration
ORM-tables, deterministic assignment 2 days
Event tracking, sales integration 2 days
Statistics, χ² test 2 days
JavaScript SDK for frontend 1 day
Administrative interface 2 days
Testing 1 day

Total: 10 working days. Multivariate testing (3+ variants) and server-side feature-flag service — additional estimate required.