Reading Time: 3 minutes


AngularJS sites are a joy to build, maintain and visit. But testing them? “Joy” is probably not the word. The dynamic, single page nature of these sites means that most of your standard testing approaches have to be thrown out the window. Here are a few common mistakes and how to prevent them.

Using the Optimizely Editor

Don’t do it! Everything might look fine in preview mode, but for reasons outlined below, it’s unlikely that the changes will be consistently visible once you launch the experiment. You’re gonna have to write some code!

Attempting to modify elements before they’re present on page

Your site probably loads quickly, which is great for your visitors but confusing for the Optimizely snippet. If your experiment code modifies elements that are added dynamically after page load, it may work in local preview mode — it may even work sometimes when the experiment is running. But the delay between DOM ready and the element’s actual appearance is a dangerous time during which your code will happily execute, find nothing to modify, and return.

You can solve this problem with a simple polling pattern, but at Cro Metrics we usually take advantage of our own Eric Newland’s jQuery-onMutate plugin. Whatever route you choose, just be sure you take nothing for granted in terms of what’s on the page when your experiment runs.

Modifying inputs without calling window.jQuery

If your code involves filling form inputs, you can’t get away with a simple

$('input-selector').val('New Value');

That’s because $ in this context refers to Optimizely’s built-in version of jQuery and Angular won’t be listening for changes from elements selected by that library. Instead, try
window.jQuery('input-selector'.val('New Value').change();
to make sure your update registers and passes site validation. (This is as good a time as any to remind you that window.jQuery itself may or may not be present at the time your experiment code executes.)

Writing code that only runs once per page load

A typical A/B test involves running a script every time a page loads. If a visitor navigates back to the page, the page loads again, the script runs again, and they see the variation again. Perfect. AngularJS sites don’t work that way, though.

Navigation in an Angular site is not about page loads — it’s about routes and views. Imagine a visitor navigates to a page on your site, your experiment code runs and they see the variation. So far so good. But then they navigate to another part of the site and come back. There’s no page load, so your code won’t run again. They’ll see the original (unmodified) version of the page. There go your results!

One way to account for this is to acquire $scope from an Angular element, then watch for location changes. It looks like this:

var myElement = document.getElementById('element-id');
var $scope = window.angular.element(myElement).scope();
$scope.$on('$locationChangeSuccess', function(event, next, current) {
  if (next.indexOf('/your/test/page') !== -1) {

Failing to acquire $scope (for every 500th visitor)

The trick of acquiring $scope is a handy one; in addition to watching for location changes, you can use it to validate and submit forms, or do any number of things that your site already does on its own. Just watch out — it’s possible that your experiment code runs before window.angular is available. It’s also possible for the call to scope() to fail, depending on where in the Angular digest() loop the call is made. So be sure to wrap every such call in a series of cascading checks, like

if (!window.angular ||
               !window.angular.element(myElement) ||
               !window.angular.element(myElement).scope()) {
                 // setTimeout and try again

This is just a starter list of the considerations to be aware of when testing AngularJS sites, but we hope it’ll keep you from doing anything too embarrassing. If you’re struggling to implement an A/B test on an Angular site, give us a call. We’ll prevent these common pitfalls, along with a few dozen less common ones we’ve encountered 😉