Skip to main content

A/B testing on modern frontend frameworks

note

The current recommended approach to run experiments / feature flags on frontend frameworks is to use one of our client-side SDKs depending on the framework you use. If you have not subscribed to the Feature Experimentation module, or if you prefer not to install a SDK in your codebase, this article describes a workaround to run experiments using only the Activation API.

Context

Modern JavaScript client-side frameworks (React, Angular, Vue...) manage the DOM tree and elements themselves (and often implement a virtual DOM on their side). This makes it very difficult to implement client-side tests outside of the framework being used, since any kind of DOM manipulation will usually conflict with the internal DOM management provided by the framework. Typically, if you modify elements or add logic from the outside, those changes will be erased / overwritten later.

These problems can be worked around in most cases, for instance by using DOM Mutation Observers, but your outside code needs to be extremely careful. This can quickly become very tedious and counter productive. Rather than being intrusive from the outside, it can be much easier to implement the variations directly in your usual React / Angular / Vue codebase, and trigger the experiments programmatically. The logic used is almost the same than for our SDKs - in practice, Kameleoon frontend engine acts here as the equivalent of our JavaScript SDK.

note

Using the techniques described in this article will require a redeployment of your JS source code to actually launch an experiment or personalization, since the implementation of the variation will be done on your usual codebase. So while the actual coding will be much easier and cleaner, you cannot benefit of the flexibility brought by the usual Kameleoon methodology (namely, the ability to launch A/B tests without involving IT deployments). Obviously, the implementation also has to be performed by a developer who has access to the code repository of the frontend application.

Of course, you can always also use the traditional Kameleoon way of implementing JS experiments from the outside (in Kameleoon's injected code) or even mix both kind of approaches.

Installation of the Activation API

To use the Activation API methods presented here, you will need to install Kameleoon in your client-side application by following this documentation.

info

The installation process is the same as for a standard website, even if you're using a web-application (sometimes called a SPA): you just add an external JavaScript application file, which contains our Kameleoon engine and the Activation API.

In any case, we recommend making sure that Kameleoon is fully loaded before running any application code that will trigger experiments. You should listen to the Kameleoon::Started event for this purpose. Another alternative (not the best approach in our opinion) is to load the Kameleoon script synchronously in your HTML loader page, before your framework code.

Creation and configuration of the experiment

All steps are similar to a classic A/B experiment (creation of variations, choice of goals and analytics integration, etc). The only thing worth noting is that you should:

  • create an experiment with the code editor
  • select the Explicit trigger targeting condition on your segment, since you will trigger the experiment manually using the API.

You will also need to note down the IDs of the experiment and of the variation to use them in your code.

note

The JS code for the different variations should usually be left empty, as it's unneeded in most cases. However, it can be very useful to include JSON data in your variations because it can then be retrieved in your application code as parameters. Those parameters can easily be changed in Kameleoon interface without requiring further redeployments.

Sample Code to trigger an experiment

The code sample below explains how to use our API to trigger an experiment and obtain the associated variation for the current user. Note that while the experiment is not launched via the Kameleoon interface, Kameleoon.API.Experiments.getAll() won't include it in the array of live experiments. This allows you to prepare your code deployment (containing the test implementation) and launch your experiment independently. Typically, the deployment will be planned by the IT team and the actual experiment launch will be sheduled by a product owner. This can also be used for feature flagging, for instance.

var variationID;

// Make sure Kameleoon is loaded and active at this point
var experiment = Kameleoon.API.Experiments.getById(75253);

/*
The experiment is not currently live (not launched yet, or paused).
The default behavior should take place.
*/
if (!experiment) {
variationID = 0;
} else {
Kameleoon.API.Experiments.trigger(experiment.id, false);

/*
The experiment is not activated, which usually means the user has not been
assigned a variation (part of the traffic was not assigned to any variation),
or capping requirements are not met. Again, the default behavior should happen.
*/
if (!experiment.associatedVariation) {
variationID = 0;
} else {
variationID = experiment.associatedVariation.id;

/*
Illustration of the JSON parameterization technique - the additionalParameters
variable will now be equal to the JSON content we set in Kameleoon's back-office.
*/
var additionalParameters = Kameleoon.API.Variations.execute(variationID);
}
}

// The following code implements the changes related to the experiment

if (0 == variationID) {
// This is the default / reference number of products to display
recommendedProductsNumber = 5;
} else if (148382 == variationID) {
// We are changing number of recommended products for this variation to 10
recommendedProductsNumber = 10;
} else if (187791 == variationID) {
// We are changing number of recommended products for this variation to 8
recommendedProductsNumber = 8;
}

To retrieve variation dependent JSON data in your application code, the content of the JS variation code entered in the Kameleoon interface should be something like:

return {timeout: 500, errorMessage: "Something went wrong", productIDs: [536, 892]};