Skip to main content
All CollectionsFor WebBuildAdvanced Editor
Optimize Build Framework (OBF)
Optimize Build Framework (OBF)
Updated over 11 months ago

Sections:

Introduction

The Optimize Build Framework (OBF), is a framework that faciliates easier coding in our Advanced Editor. Pre-defined methods, scripts and modules are tailored for use in our platform.

The OBF abstracts away the more challenging aspects of building a test, providing the simple-to-understand methods documented herein.

Why use a framework?

Pros:

  • Simpler code – Doing complex things becomes a lot more straightforward with the utility methods provided as part of the OBF.

  • Code consistency – if you have a team of developers, all of their work starts to look more similar. Debugging someone else’s work becomes a lot more straightforward too.

  • Rich debugging and error handling – Console logging and alerting are hidden from end users behind a developer cookie. Error handling is more graceful, being picked up inside sandboxed function calls and logged to developers so they can investigate.

  • Helper methods – Beyond what we provide in the tag, the OBF has a range of helper/utility methods to make certain things much more straightforward. The module ecosystem also allows you to write your own.

  • Building better tests – With polling, page hiding, granular control over View tracking/entry, etc. – you can build much more robust tests.

Cons:

  • It’s a big framework, so there’s a learning curve to go through.

  • Working with bundlers - some people use Webpack or similar tech to bundle their code locally. This would be another layer to integrate on top of that.

  • Weight – the expansive code can make it feel like overkill at times, if the job is quite simple.

Before you get started – enable the integration

We don't add anything unnecessary to your tag, and the Build Framework is one of these optional things to include only if you're using it. Not all customers use it, and so we don't include it by default.

If you don't have the integration enabled, you'll see error messages similar to "WTOBF is not a constructor" when trying to build an experiment.

Details of how to do so are documented at: Integrations - Optimize Build Framework.

Structure of a build

Normally, you would put the code for your variation into Level Content. This is not the case with OBF. It uses a single JS file to handle all of the code you need to manage (in post-render, see below), and the remaining sections just contain constructors and references.

  • Pre-Render – Initialises the namespace that we use for your test. Will lift a few settings out of Velocity to help simplify things too.

  • Level Content – Simply sets the reference in the OBF using setLevel method, so we know which level the user was served.

  • Post-Render – Where all of the configurable parts of your build are kept. See below.

Structure of post-render

Enabling Debug

Console.log statements in the Render section of the OBF are masked by a cookie check. This means that end-users won’t be able to see your logs, but as a developer you can drop the cookie and see what’s going on.

At the top of Render, you’ll see a mapping of var console = Test.debug, which is how this happens. By mapping console to Test.debug, you can write your transformations in the console and see the logs, safe in the knowledge that once they’re in the OBF, they won’t be visible anymore.

To enable debugging there are two routes you can take:

  • Use the query string parameter _wt.bdebug=true

  • Use the cookie _wt.bdebug=true

If you use the query string value, it will create the cookie for you, so this experience will stick with you for your session.

To help create the cookie, you can drag this link into your bookmarks bar: OBF Debug Cookie. Once copied into your bookmarks bar, simply click it and refresh the page, and console.log statements will start to appear

Configuration params

The OBF is highly configurable. The Config object in your post-render script is where most of these settings are kept. You’ll find the options and possible values listed below.


Build-specific

Param

Value

Description

baseURL

Type: String Default: ""

When referencing assets in your build, it is easier and lighter to specify the baseURL (folder) udner which they can be found, and then simply build your HTML (using JS) with code similar to: var html = '‹img alt="" src="'+ Config.baseURL +'myimage.png"'›';

testAlias

Type: String Default: As retrieved from Velocity

Storing the testAlias for the project allows us to hook into internal events systems and perform some checks seamlessly.

testName

Type: String Default: Your test name.

The OBF Debug output includes this at the very start, so you know exactly what you're looking at when debugging.

developer

Type: String Default: Your name.

When looking into debugging logs, you'll know who built the test. Useful for anyone developing to know who to go to for problem resolution when they see an error.

Conversion Tracking

Param

Value

Description

convDelay

Type: Integer Default: 800

If delaying conversions with Test.conversion, this is the time we will wait before redirecting.

cpoints

Type: Boolean Default: false

If true, highlights DOM nodes which have the classname specified in cssConvNode.

cssConvNode

Type: String Default: .wto-cp { border: 2px solid orange !important; }

CSS rule to highlight conversion nodes – the class (.wto-cp by default) itself has to be assigned manually by developer to nodes in Tracking() for this to work.

useBeacon

Type: Boolean Default: false

Uses beacon mode for conversions. Similar to 1px GIF tracking.

useCTrack

Type: Boolean Default: false

Use CTrack for conversions (REST API) instead of in-tag methods (JS API).

skipPageviewCheck

Type: Boolean Default: false

We need a View before we track a Metric. The OBF will automatically and safely reject conversions fired before then, but you can request to skip the check.

Page Hiding

Param

Value

Description

cssHide

Type: String Default: body { opacity: 0.0000001 !important; }

The CSS rules we want to employ when page-hiding in the OBF.

cssHideID

Type: String Default: ""

ID of the style tag that houses the page hide.

hidePage

Type: Boolean Default: true

Whether or not to employ page hiding. If your test has transformations behind user actions or on content that mutates into view, you may want to leave this off.

hideTimeout

Type: Integer Default: 7000

If the worst happens and the build freezes processing, this number defines the time in miliseconds that we should wait before forcibly redisplaying the page.

hideTimedOut

Type: Boolean Default: false

A status. Will be true if the page hide was forcibly removed.

showPage

Type: Boolean Default: true

Whether you should rediaplay the page after Rendering or not. If you're doing a redirect test, the answer may well be to leave page-hide on.

showPageDelay

Type: Integer Default: 0

If you want to wait for a period of time before revealing the page, you can do that here (in miliseconds). No delay by default.

CSS module

Param

Value

Description

cssPrefixClass

Type: String Default: .wto

By default, CSS rules added via. css.add will be prefixed with this class. This is to increase the specificity of our rules above those listed, and increases the likelihood of them being selected by the browser.

classNamesOnDoc

Type: Boolean Default: false

If set to true, level-specific class names will be added to the ‹html› tag instead of default ‹body›. Sometimes this is desired as in some cases additional classes on body might break default site functionality.

Debugging

Param

Value

Description

debug

Type: Boolean Default: false

Forcibly show debug logs in the console for this instance of the OBF. Skips the check for the _wt.bdebug cookie.

debugInTitle

Type: Boolean Default: true

When true, we output level and test names into the document title. This only happens if in debug mode.

outputConfig

Type: Boolean Default: true

Outputs the Config object for your build into the console when your build executes.

debugPrefix

Type: String Default: null

Provides the ability to output something else to prefix debug logs instead of the testAlias.

showInternalEvents

Type: Boolean Default: false

Enables the event module to log internal events in the console. Possibly useful for debugging.

pvDebug

Type: Boolean Default: false

Enables extra debugging to understand what's happening with the Pageview event.

Helper methods

Cookies

.get

Retrieves the value of a cookie, or null if no value is found.

Test.cookie.get(str_cookieName);

.del

Deletes the cookie with a given name.

Test.cookie.del(str_cookieName);

.set

Creates a cookie with these params:

  • Name: required, string. Name of the cookie

  • Value: required, string. Value of the cookie

  • Duration: optional, number. How long in days the cookie should expire in. If not provided, it will be a session cookie.

  • Path: optional, string. The pathname param in the cookie

  • Domain: optional, string. The domain param in the cookie

// General format
Test.cookie.set(str_name, str_value, num_duration, str_path, str_domain);

// Session cookie
Test.cookie.set("myCookie", "the value");

// Persistent Cookie - 30 days
Test.cookie.set("myCookie", "the value", 30);

CSS

The OBF comes with references to a css method. This allows input either by a single String of CSS (with however many rules you like, or an Array of strings.

// Single input

css.add('#mybutton { background: red; } .secondarybutton { background: grey }');

// Multiple input

css.add([

'#mybutton { background: red; }',

'.secondarybutton { background: grey }'

]);

For general use, this is similar to WT.helpers.css.

By using Javascript to write your CSS, you can easily make your rules conditional:

var col = window.something ? 'red' : 'blue';

css.add('#mybutton { background: '+ col +'; } .secondarybutton { background: grey }');

Polling

Polling is a recursive check for a condition, such as waiting for an element to show up. This is made simple in the OBF:

Test.poll({
msg: 'Polling for X', // message for console
when: function(){ ... }, // condition
then: function(){ ... }, // do something
timeout: 10000, // optional, max time to wait
forceExec: false, // optional, force "then" to run on failure
onTimeout: function(){ ... } // optional, do something on timeout
});

As you can see, some arguments are optional, but the key parts are:

  • When

  • Then

  • Msg

The full list of parameters and their definitions are:

  • when: required, function. Should return the condition you're polling for so we know what success is.

  • then: required, function. What you want to happen if we succeed.

  • msg: optional, string. Message to write to the console while polling.

  • timeout: optional, integer. Default 7000. How long to poll for before breaking.

  • forceExec: optional, boolean. Default false. Whether or not we should execute "then" on failure.

  • onTimeout: optional, function. To be executed on failure to match the "when" condition.

Example: Poll for jQuery and the footer

// Poll
Test.poll({
when: function(){
return window.jQuery && jQuery('footer').length;
},
then: function(){
// Do something
},
msg: 'Polling for jQuery and footer'
});

Modules

What are modules in the Optimize Build Framework?

Modules are a way to extend the OBF, making more methods available within the Test object.

The principle is that these modules are added to a given test, and this separates functionality that you could make use of in a standard format. If you choose to add the functionality to another test, you can simple copy the module over. Or, store the module somewhere global and simply add it to your instance.

For example, you might want to create a helper for a building Popups, which you could make available under Test.popup, where with a few parameters supplied (name, description, image, button), you can output a consistent popup that's controlled from a single place.

Building your own modules

The code to add a new module to your instance is:

WTOTestx.addModule("module_name", function(Test, Config){
return {
method1: function(){ ... },
method2: function(){ ... }
}
});

Taking our popup example, we could build the following:

 WTOTestx.addModule("lightbox", function(Test, Config){
return {
create: function(o){
document.body.insertAdjacentHTML('beforeend', `
<div id="my-popup" style="background-image: url(${o.img});">
<h3>${o.title}</h3>
<p>${o.body}</p>
</div>
`);
},
delete: function(){
var el = document.getElementById('my-popup');
if(el) el.parentNode.removeChild(el);
}
}
});

Once added to your test (we suggest at the bottom of pre-render), you can call these methods easily in your test:

// To create the popup
Test.popup.create({
title: 'Offer just for you!',
body: 'Heres a great offer just for you',
img: '/assets/popupbg.png'
});

// To delete the popup
Test.popup.delete();

Event system

What is the Event system in the Optimize Build Framework?

The OBF comes with it's own internal events system, allowing a good degree of awareness of events fired, as well as the ability to fire and listen to your own events.

There are many useful applications of this, e.g. When someone clicks a button you built, fire an event. Have multiple subscribers - one to handle validation/submission, one to track a metric, etc.

How does the Event system work?

There are two key aspects to the event system.

  1. Triggering - this is the mechanism through which events are fired.

  2. Listening - this takes place through the mechanisms of bind, bindAfter and once, each of which are detailed below.

As long as you have a listener in place at the time an event is fired, all three listener types will fire. If you retroactively apply a listener, only bindAfter will trigger.

Firing Events

Quite simply, this is:

Test.event.trigger("event_name");

This can happen at any time, whether inside of Run, Render or Tracking.

You can attach extra data to this event, e.g.:

Test.event.trigger("event_name", {
param1: "value1",
param2: "value2"
});

Subscribing to Events

Using .bind

This allows you to hook into events that will trigger, but have not yet.

Quite simply, the markup is:

Test.event.bind("event_name", function(data){
// data = { param1: "value1", ... }
});

Using .bindAfter

This works exactly as above, except that it also works retrospectively.

Test.event.bindAfter("event_name", function(data){
// data = { param1: "value1", ... }
});

Using .once

As the name suggests, any callback given to this method will fire once and then detach, whereas the others will fire every time an event of that name/description takes place.

The markup is still very consistent with the above:

Test.event.once("event_name", function(data){
// data = { param1: "value1", ... }
});

Scenarios in Detail

Conditional Activation / Manual Entry / Pageview Handling

We've made this nice and simple. The Run function specifies test execution, and when coupled with some handy helper methods allows you to choose when to activate your test.

If your condition is available to read before the tag triggers

The below method ensures that no visitors are included in the test if they match our condition. They will continue to be tracked if they have been counted previously, though.

Run: function()
{
if(document.cookie.match(/userType=business/i)){
Test.abort("We don't want to include business users.");
return;
}

Test.poll({
msg: 'jQuery + body polling',
// Polling function
when: function()
{
return window.jQuery && jQuery('footer').length;
},
// Polling callback
then: function()
{
Test.start('Rendering', Test.Render);
Test.start('Tracking', Test.Tracking);

// SHOW PAGE
Test.showHidePage(Config.showPage);
}
});
},

If you need to wait for an element before knowing if it's ok to run your test

In this example, we show you how to run a test only if your page has errors on it. Again, visitor are only counted if meet our condition.

Run: function()
{
Test.pageview.suspend("waiting for an element before we know it's ok to proceed");

Test.poll({
msg: 'jQuery + body polling',
// Polling function
when: function()
{
return window.jQuery && jQuery('footer').length;
},
// Polling callback
then: function()
{
if(!jQuery('label.error').length){ // If no errors, abort the test
Test.abort("No errors found");
return;
}

Test.pageview.track(); // Fine to count visitors now.

Test.start('Rendering', Test.Render);
Test.start('Tracking', Test.Tracking);

// SHOW PAGE
Test.showHidePage(Config.showPage);
}
});
},

If you need to wait for an event before activating your test

In this example, we wait for someone to click on an element before we activate the test.

Run: function()
{
Test.pageview.suspend("waiting for a click before we know it's ok to proceed");

// Start by polling for jQuery
Test.poll({
msg: 'jQuery polling',
// Polling function
when: function()
{
return window.jQuery;
},
// Polling callback
then: function()
{
// Set your event hook
jQuery('body').on('click', '#myelm', function(){

Test.pageview.track(); // Fine to count visitors now.

Test.start('Rendering', Test.Render);
Test.start('Tracking', Test.Tracking);

// SHOW PAGE
Test.showHidePage(Config.showPage);
});
}
});
},

As you can see from these examples, the crucial parts to this process are:

  1. Suspend the pageview - telling us to wait to be instructed instead of auto-counting the user

  2. Track the pageview - when instructed only

Everything around when you track the view is completely up to you - you just need to make sure you suspend as the first thing you do, without any delay.

Page hide

There are there key components to how Page Hide / Masking works, that you have control over.

Config.cssHide

This is a value found at the top of every post-render script, in the Config block. Here, you'll find a string which you can set to any CSS rule/s. When/while masking is enabled, this is the CSS we will write to the page.

It could be an entire body tag, e.g.

body { opacity: 0.0000001 !important; }

Or it could be a specific element or container, e.g.

section#contact-form { opacity: 0.0000001 !important; }

We recommend to use Opacity, and a near-zero value, such as 0.0000001. This is allow elements to be technically "visible" if Javscript ever queries the state of an object, but no user will see an element that's 0.00001% visible. This means the page is unlikely to break.

There are also numerous other ways of hiding elements, such as using Display, Visibility, or shifting the element out of view, but all of these come with the drawbacks that this specific value of Opacity overcomes as described above. Everything is the right size, technically visible, in the right place, etc., it's just that a user won't see it.

Config.showPage

This is described above, in Config > Page Hiding.

Test.showHidePage

This is the function that triggers a hide or show.

When the OBF post-render first executes, it will trigger a "hide" behind the scenes.

In the Run function, you'll see after Render and Tracking have been called, we then reveal the page to the user through this function. We pass in Config.showPage as an argument, which is defaulted to true but if set to false we can make sure the page isn't revealed. Or, you could simply remove this line entirely.

Rendering levels

All rendering of test content happens within the Render function (note the capital R).

For each level/variation, you will then find:

Test.render("level_name", function(level){
// do something
});

In the above example, level_name refers to the same name you provide in the level content.

This is typically in the format of FnLn, where:

  • F refers to Factor, a category of change. AB(n) tests will only have one, but MVTs will have F2, F3 etc.

  • L refers to Level, a variation (plus control). These are typically numbered 1, 2, 3 etc.

The framework selectively executes these functions, based on whichever variations the user has been enrolled into. This is why you can have multiple Test.render functions in the same block:

Test.render("F1L1", function(level){
// Something just for the control group
});

Test.render("F1L2", function(level){
// Something just for the variation
});

This is entirely optional, though - if you want to exclude Test.render functions for variations where you're doing nothing, you absolutely can do.

Redirection tests (split url testing)

The key to redirect tests it that you want to capture the View for the user before redirecting them away, but also don't want the automatic page-reveal to cause content flickering.

There are a few pieces to this:

  • Config.showPage = false - this will stop the page from being redisplayed automatically.

  • Test.event.bindAfter("pageview" - this will wait until the view has tracked for the user

  • location.replace - This will redirect the user away, and preserve the back-button functionality with the new URL taking the place of the current one in the user's browse history.

Put together, your code would look like:

Test.render("F1L2", function(level){
Config.showPage = false;
Test.event.bindAfter("pageview", function(){
location.replace("/new-pathname-to-go-to");
});
});

Conversions & custom data collection

Details of how to do this can be found at: Metric Capture in the Advanced Editor - Using the OBF

Did this answer your question?