How can you manually "trigger" an A/B test, via the API?
We have some situations where we're retrieving content via the API in the controller and using it to vary the output. Sometimes, this content is in an active A/B test. However, since the content isn't being directly sent back in the response (rather, it's being used to make decisions on other output, so that content never leaves the controller), the A/B test never "activates," and doesn't look for a conversion.
Given any IContent object, I need a way to tell the A/B testing framework that "this content has been 'triggered' by this request, and you should look for and track the conversion."
Within the EPiServer.Marketing.Testing.Core.Manager namespace there is a TestManager class (ITestManager interface) that contains the API methods for operating on an AB test. Included is an IncrementCount method that allows you to update conversions for the specified AB test, version, and KPI you want to increment. You can get an instance of this class through the ServiceLocator as well.
Jason, can you explain the logic for how an A/B test "activates"? Specifically --
Yes, the AB framework hooks into the CMS LoadedContent Event to determine if a piece of content being loaded on a request is part of an Active AB test (test data with the State of "Active" as opposed to "Inactive", "Done", or "Archived"). Then if it is it will determine if the user should be considered in the test based on participation percents, then which version of the content (A or B) the user should see. We then attach our AB cookie to the response to make sure that subsequent requests for that content during the test duration remain consistent so that people who see version B always see version B until the test ends and a winner is chosen and published.
Conversions on the server side are handled by whatever .NET Event the particular conversion goal (KPI) is interested in. The KPI is developed so when it is initialized as part of a test the AB framework will pass the KPI an Event Handler to attach to a Event so that when the Event is fired the AB framework will call Evaluate on the KPI to see if a conversion should happen. If evaluate returns a successful conversion the result is logged and the cookie for the request that triggered the successful conversion is updated so that multiple conversions don't happen for the same user (unless the always eval flag is set on the KPI which says that multiple conversions are allowed for this KPI).
If you are handling the conversion manually through the API though you wouldn't have to worry about the KPI event to attach to, but you would want to keep track of the same user converting multiple times if that is not desired behavior.
"We then attach our AB cookie to the response..."
That's the part I'm confused about. When does this happen? It does not seem to happen by just pulling content from the API.
Yes, when we determine when a new visitor should be included in the test and which version they see in the LoadedContent Event is when we write the cookie to the response.
So, simply retrieving content from the API somewhere during the request should, in theory, write a cookie to the response and activate that test? Like this...
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();var myBlock = contentLoader.Get<BlockData>(myContentLink); //Cookie is written here?
Yep, the test has to be in an Active state, but it should trigger all the logic for including determining if the request should be counted as a view.
So, here's what I'm seeing.
I created a controller like this:
public class TestController : Controller
public string Index(int id)
Response.ContentType = "text/plain";
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
var thisContent = contentLoader.GetChildren<IContent>(new ContentReference(id)).First();
So, I can call "/test/5" or whatever, and it will return the Name property of whatever content is under that ID. Note that this is a"pure" API retrieval. The content (for all it knows), is never being output to a view. Rather, it's being returned as a string.
I changed the title of the Alloy Plan page to something new, then started an A/B test with 100% participation.
I popped multiple incognito windows and pulled the controller. I got roughly half of the control and the challenger, which is to be expected.
However, the A/B test never registered a view. This is what I saw the last time I tried it too (under less controlled conditions). When you pull the content via the API, you will get different versions based on your test (this is good), but it won't track views or conversions (this is bad). Clearly, there's no chance for a conversion here, but it should at least track the views.
Am I doing something wrong?
Ahh, I think I see what is happening. You are loading a Page object through the API correct? For Pages, and Pages only, we check that the Page being loaded through LoadedContent matches the ContentLink of the Page under test. This is to avoid the situations where simply loading the title for the Page for navigation purposes (like the alloy site does for it's navigation) would trigger a View for that Page's test. What was happening is that every Page under test in an Alloy site would get a View simply by going to the Start Page and loading the other Pages for the title for navigation.
We don't do this check for Block and Product content because there is no easy way to determine if the block or product being loaded is being used in full or used in part whereas Page objects do have that information available through the IPageRouteHelper. We check the requested Page given to us by the IPageRouteHelper so we can check the content link of the requested Page vs the ContentLink of the Page object being loaded through LoadedContent.
So you are right, for Page objects being loaded purely through the API you would have to manually trigger the view and update the AB cookie for the request to indicate that a view has happened (the cookie will still be there but the Viewed flag will be false). Alternatively API calls for Block and Product content will trigger views as it would normally so you could use those objects instead of Page objects for items that are only served through controllers with routes outside of registered Episerver routes.