2

We are experiencing an issue when running Selenium tests with GhostDriver. We have a Selenium test failure which is influenced by the technologies we've chosen. Our stack is a Lift backend supported by an AngularJS front-end. We are utilizing CometActors to push data to two different AngularJS controllers on the front-end.

When a user visits the site, they will first see the home page which is served by controller one. This controller renders the page with many navigable elements. The second controller is linked to by the links generated (containing $routeParams) on the homepage.

Our test failure appears as we load the page for our Model List page. This page contains the main controller which binds the data we receive from Comet to the template. When the items are visible on the page, we can click them to transition to the second controller. This controller makes a new request to our Lift backend which causes the CometActor to send a response back to the front end. We have verified that everything functions as we expect up to this point including the initial Comet request to retrieve a list of models and the corresponding response meant for the front-end.

The issue appears here... The front-end never executes the JavaScript command supplied in the response from our CometActor. Our CometDispatch.sendModels function is never invoked, preventing an event from triggering an update to the ModelListController. Our Selenium test fails because it cannot find the expected HTML element which should be created when the ModelListController is updated. We are unsure of what happens to the JavaScript command supplied by Comet as it appears it is never executed in the front-end. We verified that our list of Models was not rendering by dumping the page source produced by GhostDriver to the console.

This problem has caused us much confusion as our selenium test passes when driven with Firefox.

CarRegistry.scala

class CarRegistry extends CometActor {
  implicit val formats = DefaultFormats
  private val adapter: ModelAdapter = new LiftActorModelAdapter

  override def lifespan = Full(5 minutes)

  def render: RenderOut = {
    adapter.handleMessage(ListMakesMessage(this))
    "#car-registry-uuid [value]" #> SHtml.jsonCall(Call("function(){}"), fetchModelsByMakeId _).guid
  }

  def fetchModelsByMakeId(requestJValue: JValue) = {
    requestJValue match {
      case JInt(makeId) => adapter.handleMessage(ListModelsMessage(this, makeId.toLong))
      case _ => ()
    }
    _Noop
  }

  override def lowPriority = {
    case s @ JObject(List(JField(key, JArray(_)))) if key =="make"  =>
      partialUpdate(Call("CometDispatch.sendMakes", s))
    case s @ JObject(List(JField(key, JArray(_)))) if key =="model" =>
      partialUpdate(Call("CometDispatch.sendModels", s))
    case _ => ()
  }
}

comet-dispatch.js

var CometDispatch = {
    sendMakes: function(makesJson) {
        $('body').trigger('makes-received', makesJson);
    },
    sendModels: function(modelsJson) {
        $('body').trigger('models-received', modelsJson);
    }
};

app.js

'use strict';

angular.module('myApp', [
  'ngRoute',
  'myApp.filters',
  'myApp.services',
  'myApp.directives',
  'myApp.controllers'
]).
config(['$routeProvider', function($routeProvider, $routeScopeProvider) {
  $routeProvider.when('/', {templateUrl: 'static/pages/make-list.html', controller: 'MakeListController'});
  $routeProvider.when('/:makeId/list', {templateUrl: 'static/pages/model-list.html', controller: 'ModelListController'});
  $routeProvider.otherwise({redirectTo: '/'});
}]);

controllers.js

'use strict';

angular.module('myApp.controllers', []).
    controller('MakeListController', function($scope, DataStore) {

        $( 'body' ).on( 'makes-received', function( event, makesJsonObj ) {
            $scope.$apply(function() {
                DataStore.makes = makesJsonObj['makes'];
                $scope.makes = makesJsonObj['makes'];
            });
        });

        $scope.makes = $scope.makes || DataStore.makes;
    }).
    controller('ModelListController', function($scope, $routeParams, CometRequestService) {

        $( 'body' ).on( 'models-received', function( event, modelsJsonObj ) {
            $scope.$apply(function() {
                $scope.models = modelsJsonObj['models'];
            });
        });

        CometRequestService.getModelsFor($routeParams.makeId);
    });

services.js

'use strict';

angular.module('myApp.services', []).
  factory('DataStore', function() {
      return {
          makes: []
      };
  }).
  factory('CometRequestService', function() {
     return {
         getModelsFor: function(makeId) {
             liftAjax.lift_ajaxHandler($('#car-registry-uuid').val() + "=" + makeId, null, null, null);
         }
     }
  });

index.html

<div id="main" lift="surround?with=default;at=content">
   <h1>Model Directory</h1>

    <div lift="comet?type=CarRegistry&randomname=true">
        <input type="hidden" id="car-registry-uuid"/>
    </div>

    <div ng-view></div>
</div>

make-list.html

<div id="make-list">   
    <div ng-repeat="make in makes">
        <a ng-href="#/{{make.id}}/list/" data-dropdown="dd-{{make.id}}">
            {{make.name}}
        </a>
    </div>
</div>

model-list.html

<div id="model-list">
    <div ng-repeat="model in models">
        <div>
            {{model.name}}
        </div>
    </div>
</div>

This question is also described in this gist.

0 Answers0